1. 引言
1.1 FFmpeg简介
FFmpeg是一个自由软件,可以运行音频和视频多种格式的录影、转换、流功能,包含了libavcodec——这是一个用于多个项目中音频和视频的解码器库,以及libavformat——一个音频/视讯封装格式的解码器库。FFmpeg在编程中被广泛应用,它的强大功能使得开发者可以更加方便地处理音视频数据。
在C++领域,我们可以通过调用FFmpeg提供的API,对音视频数据进行各种操作,如解码、编码、转码、滤镜处理等。这使得FFmpeg成为音视频处理领域的重要工具。
1.2 YUV和PCM的基础知识
1.2.1 YUV
YUV是一种颜色编码方法,常用于视频系统。在YUV中,Y代表亮度(Luminance),U和V代表色度(Chrominance)。YUV的设计考虑到了人眼对亮度信息比色度信息更敏感的特性,因此在视频压缩时,可以通过降低色度信息的精度,而保持亮度信息的精度,以实现高效的压缩。
在FFmpeg中,解码后的视频帧数据通常以YUV格式存储。每个像素的颜色信息由一个Y值和一个UV值对表示,其中Y值表示像素的亮度,UV值对表示像素的色度。
1.2.2 PCM
PCM(Pulse Code Modulation,脉冲编码调制)是一种数字表示模拟信号的方法,在音频领域被广泛使用。在PCM中,模拟信号在时间上进行等间隔采样,并将每个采样值量化为数字。PCM数据可以有不同的参数,例如采样率(每秒的样本数)、采样大小(每个样本的位数)、声道数等。
在FFmpeg中,解码后的音频数据通常被存储为PCM数据。每个音频样本的数据由一个或多个PCM值表示,每个PCM值表示在一个特定时间点的音频信号的振幅。
在接下来的章节中,我们将深入探讨YUV和PCM在FFmpeg中的应用,以及如何在C++中使用FFmpeg进行音视频数据的处理。我们将通过实例和源码分析,揭示这些技术背后的原理,并提供一些实用的编程技巧。
2. FFmpeg中的YUV和AVFrame
在FFmpeg中,解码视频后的默认格式是YUV,这个YUV格式的数据是被存储在AVFrame结构体中的。下面我们将详细介绍YUV在FFmpeg中的角色,以及AVFrame的作用和结构。
2.1 YUV在FFmpeg中的角色
YUV是一种颜色编码系统,用于视频系统如电视和计算机图形。在这种格式中,Y是亮度分量(也称为灰度),而U和V是色度分量(代表颜色信息)。在FFmpeg中,解码后的视频数据通常是以YUV格式存储的。
在FFmpeg的解码流程中,解码器将编码的数据(例如,H.264编码的视频流)解码为原始的音频/视频帧,并将这些帧存储在AVFrame结构体中。对于视频,这些帧通常是YUV格式的。
下图是FFmpeg的解码流程:
2.2 AVFrame的作用和结构
AVFrame是FFmpeg中用来存储解码后的音频/视频帧的数据结构。AVFrame结构体包含了帧的数据以及一些元数据,例如,帧的宽度和高度(对于视频)、采样率(对于音频)、时间戳(PTS和DTS)等。你可以通过AVFrame的成员变量来访问这些数据和元数据。
在C++中,AVFrame的定义如下:
typedef struct AVFrame { uint8_t *data[AV_NUM_DATA_POINTERS]; // 指向帧数据的指针数组 int linesize[AV_NUM_DATA_POINTERS]; // 每行数据的大小 ... int width, height; // 帧的宽度和高度 int format; // 帧的格式(例如,YUV420P) ... int64_t pts, dts; // 时间戳 ... } AVFrame;
在这个结构体中,data
数组是指向帧数据的指针,linesize
数组是每行数据的大小。width
和height
是帧的宽度和高度,format
是帧的格式,pts
和dts
是时间戳。
2.3 YUV和AVFrame的关系
当你说“解码后都是AVFrame包”,实际上是指解码后的音频/视频帧被存储在AVFrame结构体中。这并不矛盾,因为AVFrame是用来存储解码后的帧,而这些帧的格式通常是YUV(对于视频)。
在FFmpeg中,解码器将编码的数据解码为原始的音频/视频帧,并将这些帧存储在AVFrame结构体中。对于视频,这些帧通常是YUV格式的。因此,你可以将YUV数据看作是存储在AVFrame中的原始视频数据。
例如,如果你想从AVFrame中获取YUV数据,你可以这样做:
AVFrame *frame = ...; // 已经解码的帧 uint8_t *y_data = frame->data[0]; // Y数据 uint8_t *u_data = frame->data[1]; // U数据 uint8_t *v_data = frame->data[2]; // V数据
在这个例子中,frame->data[0]
、frame->data[1]
和frame->data[2]
分别指向Y、U和V数据。你可以直接使用这些数据,或者将它们转换为其他格式(例如,RGB)。
总的来说,YUV和AVFrame在FFmpeg中的关系是:YUV是解码后的视频数据的格式,而AVFrame是存储这些数据的结构体。
3. YUV数据的处理
在本章中,我们将深入探讨如何在FFmpeg中处理YUV数据。我们将首先讨论如何直接使用YUV数据,然后讨论如何将YUV数据转换为RGB数据,最后讨论如何优化YUV数据的处理。
3.1 YUV数据的直接使用
如果你的显示设备或渲染库可以直接处理YUV格式的数据,那么你可以直接从AVFrame中取出YUV数据进行显示,无需进行任何转换。这种情况下,你的代码可能会类似于以下的样子:
// 假设frame是一个已经解码的AVFrame AVFrame *frame = ...; // 获取YUV数据 uint8_t *y_data = frame->data[0]; uint8_t *u_data = frame->data[1]; uint8_t *v_data = frame->data[2]; // 使用YUV数据 display_yuv_data(y_data, u_data, v_data, frame->width, frame->height);
在这个示例中,我们首先从AVFrame中获取YUV数据,然后将这些数据传递给一个名为display_yuv_data
的函数,该函数负责将YUV数据显示出来。
3.2 YUV到RGB的转换
然而,许多常见的显示设备和渲染库(例如,SDL、OpenGL)通常只能处理RGB格式的数据。在这种情况下,你需要将YUV数据转换为RGB数据才能进行显示。FFmpeg提供了一个名为swscale的库,可以用来进行这种转换。
以下是一个使用swscale库将YUV数据转换为RGB数据的示例:
// 假设frame是一个已经解码的AVFrame AVFrame *frame = ...; // 创建一个新的AVFrame来存储RGB数据 AVFrame *rgb_frame = av_frame_alloc(); rgb_frame->format = AV_PIX_FMT_RGB24; rgb_frame->width = frame->width; rgb_frame->height = frame->height; av_frame_get_buffer(rgb_frame, 0); // 创建swscale上下文 struct SwsContext *sws_ctx = sws_getContext( frame->width, frame->height, (AVPixelFormat)frame->format, rgb_frame->width, rgb_frame->height, (AVPixelFormat)rgb_frame->format, SWS_BILINEAR, NULL, NULL, NULL); // 将YUV数据转换为RGB数据 sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, rgb_frame->data, rgb_frame->linesize); // 使用RGB数据 display_rgb_data(rgb_frame->data[0], rgb_frame->width, rgb_frame->height); // 释放资源 sws_freeContext(sws_ctx); av_frame_free(&rgb_frame);
在这个示例中,我们首先创建了一个新的AVFrame来存储RGB数据,然后创建了一个swscale上下文,用于进行YUV到RGB的转换。然后,我们使用sws_scale
函数将YUV数据转换为RGB数据,最后将RGB数据显示出来。
3.3 YUV数据的优化策略
YUV到RGB的转换是一个计算密集型的操作,可能会消耗一定的计算资源。因此,如果可能,最好直接使用YUV数据进行显示,以避免这种转换。如果必须进行转换,你可以考虑使用一些优化策略,例如,使用硬件加速、预先转换和缓存转换结果等,以减少转换的开销。
以下是一些可能的优化策略:
- 硬件加速:一些硬件设备(例如,GPU)可以进行高效的YUV到RGB的转换。如果你的环境支持硬件加速,你可以考虑使用硬件加速来进行转换。
- 预先转换:如果你知道你将需要将YUV数据转换为RGB数据,你可以在解码时就进行转换,而不是在显示时才进行转换。这样,你可以在解码和显示之间的空闲时间进行转换,从而减少显示时的延迟。
- 缓存转换结果:如果你需要多次显示同一帧,你可以将转换结果缓存起来,而不是每次显示时都进行转换。这样,你可以避免重复的转换,从而减少计算开销。
以上就是本章的内容,我们详细讨论了如何在FFmpeg中处理YUV数据,包括如何直接使用YUV数据,如何将YUV数据转换为RGB数据,以及如何优化YUV数据的处理。在下一章中,我们将讨论如何在FFmpeg中处理PCM数据。
4. FFmpeg中的PCM和AVFrame
在音频处理中,PCM(Pulse Code Modulation,脉冲编码调制)和AVFrame是两个非常重要的概念。PCM是一种原始的、未压缩的音频数据格式,而AVFrame则是FFmpeg中用来存储解码后的音频/视频帧的数据结构。在本章节中,我们将深入探讨PCM在FFmpeg中的角色,以及如何在AVFrame中处理PCM数据。
4.1 PCM在FFmpeg中的角色
PCM是一种常用的音频数据格式,它是一种未经过压缩的原始音频数据格式。在PCM中,音频信号被分为一系列的样本,每个样本代表了在一个特定时间点的音频信号的振幅。每个样本都被量化为一个特定的数值,然后被编码为二进制数据。PCM数据可以有不同的参数,例如样本率(每秒的样本数)、样本大小(每个样本的位数)、声道数(例如,单声道、立体声)等。
在FFmpeg中,解码后的音频数据通常被存储为PCM数据。这是因为PCM数据是一种原始的、未压缩的音频数据,它可以直接被音频播放设备使用,也可以方便地进行进一步的处理和转换。
4.2 PCM数据在AVFrame中的表现
在FFmpeg中,AVFrame是用来存储解码后的音频/视频帧的数据结构。对于音频数据,AVFrame中的数据指针指向的就是PCM数据。
AVFrame结构体包含了帧的数据以及一些元数据,例如,帧的采样率(对于音频)、时间戳(PTS和DTS)等。你可以通过AVFrame的成员变量来访问这些数据和元数据。
下面是一个简单的示例,展示了如何从AVFrame中取出PCM数据:
// 假设frame是一个已经解码的音频帧 AVFrame *frame = ...; // 数据指针指向的就是PCM数据 uint8_t *pcm_data = frame->data[0]; // 样本数可以通过nb_samples来获取 int sample_count = frame->nb_samples; // 样本格式可以通过format来获取 enum AVSampleFormat sample_format = (enum AVSampleFormat)frame->format; // 根据样本格式,可以知道每个样本的大小 int sample_size = av_get_bytes_per_sample(sample_format); // 现在,你可以遍历所有的样本 for (int i = 0; i < sample_count; ++i) { // 根据样本大小,从pcm_data中取出一个样本 uint8_t *sample = pcm_data + i * sample_size; // 现在,你可以处理这个样本了 // ... }
在这个示例中,我们首先从AVFrame中取出了PCM数据的指针,然后根据样本数和样本大小,遍历了所有的样本。这就是如何在AVFrame中处理PCM数据的基本方法。
4.3 PCM和AVFrame的关系
从上述内容中,我们可以看到PCM和AVFrame之间的关系:在FFmpeg中,解码后的音频帧被存储在AVFrame结构体中,而这些帧的数据就是PCM数据。因此,当我们说“解码后的音频帧是PCM数据”,实际上是指解码后的音频帧被存储在AVFrame结构体中,而这些帧的数据就是PCM数据。
这种关系可以用下面的图来表示:
在这个图中,我们可以看到PCM数据被存储在AVFrame中,而AVFrame则被用来存储解码后的音频帧。这就是PCM和AVFrame的关系。
在下一章节中,我们将探讨如何处理PCM数据,包括如何从PCM数据中提取样本,以及如何将PCM数据转换为适合音频播放设备的格式。
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(二)https://developer.aliyun.com/article/1467277