《DangerFFmpeg》结语
本文是 《DangerFFmpeg》系列教程结语,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 还有什么?我们完成了一个可以工作的播放器,但肯定没有想象中那么好。我们做了很多功能,但是还有很多其它的能力可以添加: 让我们面对现实,这个播放器挺糟糕的。我们基于的 ffplay 早就过时了,因此这个系列的教程需要一次大翻修。如果你想更进一步,使用 FFmpeg 完成更加正式的项目,我建议你下一步就去研究最新版的 ffplay。 错误处理。我们代码中的错误处理聊胜于无,这一块有很大的提升空间。 暂停。我们无法暂停,但是暂停显然是一个非常重要的功能。我们可以创建一个暂停标识位,当用户暂停时为其赋值,然后音频视频解码线程也会检查这个变量,停止解码。我们也可以用 av_read_play 支持网络。这很好解释,但是自己可能就不太好理解,可以考虑将这个作为课后作业,如果你打算进一步学习的话。小提示,可以参考 ffplay。 支持硬件加 ...
《DangerFFmpeg》第七节、快进快退
本文是 《DangerFFmpeg》系列教程第七节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 响应快进快退操作我们现在准备给播放器添加快进快退功能,因为当你不能快退视频时确实很令人烦躁。另外,这篇教程也会让你看到 av_seek_frame 的使用非常简单。 我们让左方向键和右方向键快退或快进一点,比如10s,同时上方向键和下方向键快进或快退稍多一点,比如 60s。所以我们需要再修改下主事件循环以响应键盘事件。然而,当我们收到按键事件时,我们不能直接调用 av_seek_frame,需要在解封装循环里完成,即 decodeThread。所以,我们再往 VideoState 添加一些变量,用来表示快进快退的位置和标识位: 123bool seekReq;int seekFlags;int64_t seekPos; 现在我们需要修改事件循环,响应按键事件: 12345678910111213141516171819202 ...
《DangerFFmpeg》第六节、同步音频
本文是 《DangerFFmpeg》系列教程第六节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 我们已经完成了一个能够称得上播放器的视频播放器,所以让我们看看我们实现了什么东西。上节教程中,我们稍微优化了同步,即将视频时钟同步到音频时钟,而不是其它方式。我们将做与视频类似的事:创建一个内部视频时钟以跟踪视频线程的时间并将音频同步到该时钟。后面我们也会看到如何进行抽象,将音频和视频同步到外部时钟。 实现视频时钟现在我们想要实现一个类似上节教程中音频时钟的视频时钟:一个内部值,给出视频从播放到现在的时间偏移。起初,你可能认为视频时钟就是简单地用上一帧的 PTS 更新。然而,不要忘了视频帧中间的时间间隔在毫秒级别下会非常大。解决办法增加另一个变量,记录我们什么时候将视频时钟更新为上一帧的 PTS。视频时钟真实值是 PTS_of_last_time + (current_time - time_elapsed_since_PTS ...
《DangerFFmpeg》第五节、视频同步
本文是 《DangerFFmpeg》系列教程第五节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 上节教程中我们对代码进行了拆分,也正因如此,音视频同步实现也就比较简单了。 CAVEAT原文写作时,音视频同步的代码取自于 ffplay。现在,ffplay 发生非常大的变化,ffmpeg 也有了非常多的优化,相应地,音视频同步策略也更新了。虽然本文的代码可以工作,但是还不够优雅,还有许多地方可以优化。 视频同步原理现在为止,我们开发了一个几乎没屌用的视频播放器,它能播放音频,也能播放视频,但是还不能叫做正常意义上的播放器。所以我们该怎么做呢? DTS & PTS幸运的是,音频流和视频流里都含有应该以何种速度播放以及什么时候显示的信息。音频流有采样率,视频流有帧率。然而,如果我们简单地通过帧数和帧率相乘进行同步,有很大可能画面和音频会变得不同步。音频流中的数据包可能含有叫 DTS(decoding timestam ...
【DangerFFmpeg】第四节、多线程
本文是 《DangerFFmpeg》系列教程第四节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 概述前面我们通过 SDL 的音频功能实现了音频播放能力,SDL 启动一个子线程并在需要音频数据的时候回调我们事先定义好的函数。现在我们将用类似的方式播放视频,这可以让代码更加模块化、更好管理,特别是当实现音视频同步时。那么我们从哪里开始聊呢? 首先我们注意到 main 函数做了太多事情:事件循环、读取数据包、解码视频。所以我们要做的就是把这些分开:我们将创建一个线程负责从流(stream)中读取数据包(packet),然后把它们添加到对应的队列中,并分别在 audio 线程和 video 线程中读取。我们在第二节中已经配置好了 audio 线程;video 线程将会有一点复杂,因为我们需要自己控制视频的播放。我们将把视频播放代码添加到主循环中,但是并不是每次循环播放一帧,而是将视频播放集成到 SDL 事件中。大致的思路就是解码 ...
【DangerFFmpeg】第三节、播放声音
本文是 《DangerFFmpeg》系列教程第三节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 第二节中我们使用 send/receive 模型对视频进行编码,但是实现上不利于拓展实现这一节的音频,所以我们把问题简化,只播放音频,在下一节中再将音频和视频组合起来。 音频现在我们准备要播放声音了。SDL 也提供了播放音频对应的方法。SDL_OpenAudio 用来的打开音频设备,它接受 SDL_AudioSpec 结构体作为参数,SDL_AudioSpec 描述了我们将要播放的音频的特征。 在演示播放音频步骤之前,我们先学习下计算机是如何处理音频的。数字音频由大量 采样(samples) 组成,每个采样点表示音频波形的取值。声音以固定的采样频率进行录制,并以每秒的采样数来衡量,换句话说就是我们以多快的速度播放音频。常见的采样频率由 22,050 和 44,100 Hz,它们分别是无线电广播和 CD 的采样频率 ...
【DangerFFmpeg】第二节、输出到屏幕
本文是 《DangerFFmpeg》系列教程第二节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 同原文使用的 SDL 相比,SDL2 API 变更比较大,所以译文和原文会有一定差别。 SDL 和视频为了绘制到屏幕上,我们将使用 SDL2 框架。SDL2 是 Simple Direct Layer2 的缩写,它是个非常棒的多媒体应用开发库,支持跨平台,并且在许多项目中都有使用。你可以从官网获取 SDL2 的源码或直接将其开发包安装到系统上。从本节开始,你需要添加 SDL2 依赖才能编译教程中的代码。 macOS 上推荐 brew install sdl2 安装 SDL2。为了表达方便,如无特别说明,后文提到的 SDL 都表示 SDL2。 SDL 有许多绘制图片的方法,并且有一个适合显示视频 —— SDL_UpdateYUVTexture。YUV(准确地说是 YCbCr) 是与 RGB 类似的图像存储格式。不准确地说 ...
【DangerFFmpeg】第一节、屏幕截图
本文是 《DangerFFmpeg》系列教程第一节,系列完整目录:《开篇》《第一节、屏幕截图》《第二节、输出到屏幕》《第三节、播放声音》《第四节、多线程》《第五节、视频同步》《第六节、同步音频》《第七节、快进快退》《结语》 系列所有代码托管在 GitHub 。 概述视频文件有一些基本构成成分。首先,视频文件本身叫做 容器(container),容器的类型决定信息在文件中如何存储。AVI 和 Quicktime 就是容器的两个例子。然后,你会遇到一组 流(stream),举例来说,视频文件通常会有 音频流 和 视频流( 流 描述了 “一组按时间先后排列的数据(data element)”)。流中的数据称作 帧(frame)(帧一般会被编码成一个个包,包解开后才是帧)。每个流都是事先使用各种 编解码器(codec) 编码好的。编码器定义了原始数据是何如 编码(COded) 和 解码(DECoed) 的,所以组合起来就叫做 编码器(CODEC) 了。DivX 和 MP3 就是编解码器的两个例子。包(Packet) 紧接着从流中被读取出来。包是可以包含(有的包可能不会携带原始数据)能够被解 ...
【DangerFFmpeg】开篇
前段时间学习 FFmpeg 的时候找到一个《How to Write a Video Player in Less Than 1000 Lines》系列文章,虽说 1000 行代码的以内的播放器,但是是基于 FFmpeg 2.x 和 SDL 实现的,而自己的电脑又是安装的 FFmpef 4.x 和 SDL2,所以有很多的 API 已经过时甚至是移除了,整个过程中的阻力比较大,最后也没有完全实现这个千行播放器。最近正好时间相对比较充裕,准备重整旗鼓,用新版 FFmpeg 和 SDL 重新挑战一波,然后也顺便把这个系列翻译成中文,加深理解。 因为这个原文章的网址是 http://dranger.com/ffmpeg ,所以称这次翻译系列就叫做 DangerFFmpeg。这个系列一共包含 9 个小结,第一节也就是本文正文部分即将翻译的开篇,最后一节是总结与展望,中间 7 小结才是本系列的重点。译文中会加入一些自己的理解,如有偏差,欢迎指出斧正。 本系列的开发环境 操作系统:macOS BigSur编程语言:C++ 14FFmpeg 版本:FFmpeg 4.4SDL 版本:SDL2 已完结 ...
LeetCode遇上GoogleTest
这篇文章将记录怎么用 GoogleTest 来管理 LeetCode 的测试 case,以及优化 CMakeLists 配置避免每次都需要编译、链接整个工程,最后再利用 GitHub Action 进行自动化测试。文中不会对 Google Test 细节进行介绍,感兴趣的可以去官网阅读。 如果还不了解如何使用 VSCode 和 CMake 管理你的 C++ 工程,建议先看看这篇文章。 本文基于 macOS 10.15,Linux 环境下差别不大,后面也会提到一些 Linux 相关的配置 。 1. 前言为了方便回顾每道 LeetCode 的 AC 过程,每道题的解答都在同一个 CMake 工程中进行管理,公共数据结构(比如 ListNode、TreeNode 等)使用单独的 model 模块进行管理,不同 LeetCode 就用命名空间 leetcode_xxx 进行隔离,通过 xxx 确定某次需要运行的 LeetCode 解答,整个工程统一编译链接,构建一个可执行文件。使用这种方式管理的工程目录结构大概长这样: 12345678910111213141516├── CMakeLi ...