【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(一)https://developer.aliyun.com/article/1467276
5. PCM数据的处理
在FFmpeg中,解码后的音频数据通常被存储为PCM数据。PCM(Pulse Code Modulation,脉冲编码调制)是一种常用的音频数据格式,它是一种未经过压缩的原始音频数据格式。在PCM中,音频信号被分为一系列的样本,每个样本代表了在一个特定时间点的音频信号的振幅。每个样本都被量化为一个特定的数值,然后被编码为二进制数据。PCM数据可以有不同的参数,例如样本率(每秒的样本数)、样本大小(每个样本的位数)、声道数(例如,单声道、立体声)等。
5.1 PCM数据的直接使用
在FFmpeg中,你可以直接从AVFrame中取出PCM数据,并将其转换为适合你的音频播放设备的格式。例如,如果你的音频播放设备需要float格式的数据,你可能需要将样本从原始的PCM格式转换为float格式。
下面是一个简单的示例,展示了如何从AVFrame中取出PCM数据,并将其转换为float数组:
// 假设frame是一个已经解码的AVFrame AVFrame *frame = ...; // 获取样本大小(以字节为单位) int sample_size = av_get_bytes_per_sample(frame->sample_format); // 获取声道数 int channels = frame->channels; // 创建一个float数组来存储转换后的数据 float *data = new float[frame->nb_samples * channels]; // 遍历每个样本 for (int i = 0; i < frame->nb_samples; i++) { // 遍历每个声道 for (int ch = 0; ch < channels; ch++) { // 获取样本数据 uint8_t *sample_data = frame->data[ch] + sample_size * i; // 根据样本大小和样本格式,将样本数据转换为float float sample_value; if (sample_size == 1) { // 8位样本 sample_value = *sample_data / 255.0f; } else if (sample_size == 2) { // 16位样本 sample_value = *((int16_t *)sample_data) / 32767.0f; } else { // 其他情况 sample_value = 0.0f; } // 存储转换后的样本值 data[i * channels + ch] = sample_value; } } // 现在,data数组中存储的就是转换后的音频数据,可以传输给音频播放设备
这个示例中,我们首先获取了样本大小和声道数,然后创建了一个float数组来存储转换后的数据。然后,我们遍历了每个样本和每个声道,将样本数据从原始的PCM格式转换为float格式,并存储在float数组中。最后,我们可以将这个float数组传输给音频播放设备。
5.2 PCM数据的转换
在处理PCM数据时,你需要根据PCM的参数(例如,样本大小、样本格式、声道布局、声道数)来解释数据,并将样本转换为适合你的音频播放设备的格式。这个过程可能涉及到一些复杂的计算和转换,但是FFmpeg提供了一些工具和函数来帮助你进行这些操作。
例如,FFmpeg提供了一个名为swresample
的库,可以用来进行音频重采样和格式转换。你可以使用这个库来将PCM数据转换为其他格式,例如,将16位整数PCM转换为float PCM,或者将立体声PCM转换为单声道PCM。
下面是一个简单的示例,展示了如何使用swresample
库来进行PCM数据的转换:
// 假设frame是一个已经解码的AVFrame AVFrame *frame = ...; // 创建一个重采样上下文 SwrContext *swr_ctx = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_MONO, // 输出声道布局:单声道 AV_SAMPLE_FMT_FLT, // 输出样本格式:float 44100, // 输出样本率:44100Hz frame->channel_layout, // 输入声道布局 frame->format, // 输入样本格式 frame->sample_rate, // 输入样本率 0, NULL); if (!swr_ctx) { // 错误处理 } // 初始化重采样上下文 if (swr_init(swr_ctx) < 0) { // 错误处理 } // 创建一个新的AVFrame来存储转换后的数据 AVFrame *out_frame = av_frame_alloc(); out_frame->channel_layout = AV_CH_LAYOUT_MONO; out_frame->format = AV_SAMPLE_FMT_FLT; out_frame->sample_rate = 44100; out_frame->nb_samples = frame->nb_samples; // 假设输出样本数和输入样本数相同 // 分配数据缓冲区 av_frame_get_buffer(out_frame, 0); // 进行重采样 if (swr_convert(swr_ctx, out_frame->data, out_frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples) < 0) { // 错误处理 } // 现在,out_frame中存储的就是转换后的音频数据,可以传输给音频播放设备 // 释放资源 swr_free(&swr_ctx); av_frame_free(&out_frame);
这个示例中,我们首先创建了一个重采样上下文,并设置了输入和输出的参数。然后,我们创建了一个新的AVFrame来存储转换后的数据,并分配了数据缓冲区。然后,我们调用swr_convert
函数进行重采样。最后,我们释放了重采样上下文和AVFrame。
5.3 PCM数据处理流程图
下面的流程图展示了PCM数据的处理流程:
在这个流程中,我们首先从AVFrame中取出PCM数据,然后根据PCM参数(样本大小、样本格式、声道布局、声道数)解释数据,然后将样本转换为适合音频播放设备的格式,最后将数据传输给音频播放设备。
5.4 PCM数据处理方法对比
下面的表格总结了处理PCM数据的一些常用方法的对比:
方法 | 优点 | 缺点 |
直接使用PCM数据 | 简单,不需要额外的转换 | 可能需要手动处理样本大小和样本格式的转换 |
使用swresample 库进行转换 |
可以处理复杂的转换,例如声道布局的转换和样本格式的转换 | 需要创建和管理重采样上下文,可能需要处理错误 |
在选择处理PCM数据的方法时,你需要根据你的需求和音频播放设备的能力来选择最适合的方法。
6. 视频编码中的I帧、P帧和B帧
在本章中,我们将深入探讨视频编码中的I帧、P帧和B帧。这些帧类型是视频编码中的基础概念,理解它们对于理解视频编码和解码的过程至关重要。
6.1 I帧、P帧和B帧的定义和特性
在视频编码中,我们通常会遇到I帧(Intra-coded picture)、P帧(Predicted picture)和B帧(Bidirectionally predicted picture)这三种类型的帧。它们的定义和特性如下:
- I帧:也被称为关键帧,是自我完整的帧,不依赖于任何其他帧进行解码。I帧通常包含了图像的全部信息,因此在视频中的数据量最大。
- P帧:依赖于前面的I帧或P帧进行解码,它只存储与参考帧的差异信息。P帧的数据量通常小于I帧,因为它只包含差异信息。
- B帧:依赖于前面和后面的I帧或P帧进行解码,它只存储与前后参考帧的差异信息。B帧的数据量通常是最小的,因为它只包含差异信息。
下表总结了这三种帧类型的主要特性:
帧类型 | 完整性 | 依赖性 | 数据量 |
I帧 | 完整的 | 无依赖 | 最大 |
P帧 | 差异的 | 有依赖 | 中等 |
B帧 | 差异的 | 有依赖 | 最小 |
6.2 I帧、P帧和B帧在编码和解码中的作用
在视频编码中,I帧、P帧和B帧的主要作用是通过存储差异信息来减少数据量,从而实现视频的压缩。
例如,假设我们有一个视频序列,其中包含10帧图像。如果我们将每一帧都作为I帧进行编码,那么每一帧都需要存储完整的图像信息,这将产生大量的数据。然而,如果我们将第一帧作为I帧,然后将后续的帧作为P帧或B帧,那么后续的帧只需要存储与前一帧的差异信息,这将大大减少数据量。
在解码时,解码器会首先解码I帧,然后根据P帧和B帧的差异信息来恢复出原始的图像序列。
6.3 I帧、P帧和B帧与YUV、PCM的关系
I帧、P帧和B帧是描述帧之间的依赖关系和压缩方式的概念,而YUV和PCM则是描述帧的颜色和音频信息的方式。
在视频编码中,每一帧的图像信息通常以YUV格式存储,而音频信息则以PCM格式存储。然后,这些帧会根据它们之间的依赖关系和差异信息,被编码为I帧、P帧或B帧。
在解码后,无论原来是I帧、P帧还是B帧,都已经被转换成了完整的图像数据,可以用YUV或RGB等颜色空间来表示。这些解码后的帧都是可以独立显示的,不再依赖于其他帧。
下面是一个简单的示例,展示了如何使用FFmpeg库来解码视频帧,并获取YUV数据:
extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> } int main() { AVFormatContext *pFormatCtx = avformat_alloc_context(); // 打开视频文件 if(avformat_open_input(&pFormatCtx, "input.mp4", NULL, NULL)!=0) return -1; // 查找视频流 if(avformat_find_stream_info(pFormatCtx, NULL)<0) return -1; // 找到视频流的编码器 AVCodecContext *pCodecCtx = pFormatCtx->streams[0]->codec; AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) return -1; // 打开编码器 if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) return -1; // 分配视频帧 AVFrame *pFrame = av_frame_alloc(); // 读取视频帧 AVPacket packet; while(av_read_frame(pFormatCtx, &packet)>=0) { if(packet.stream_index == 0) { // 解码视频帧 int got_picture; avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet); if(got_picture) { // 此时,pFrame中的数据就是YUV数据 // 可以对这些数据进行进一步的处理 } } av_packet_unref(&packet); } // 释放资源 av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }
在这个示例中,我们首先打开了一个视频文件,然后找到了视频流的编码器。然后,我们读取了视频帧,并使用编码器将它们解码为YUV数据。这些YUV数据可以用于进一步的处理,例如转换为RGB数据,或者进行图像分析等。
希望这个章节能帮助你更深入地理解视频编码中的I帧、P帧和B帧,以及它们与YUV和PCM的关系。在下一章中,我们将探讨视频数据的采集和处理。
7. 视频数据的采集和处理
在本章中,我们将深入探讨视频数据的采集和处理过程,包括视频数据的采集方式,YUV和RGB在视频数据处理中的应用,以及视频数据的最小单位——帧。
7.1 视频数据的采集过程
视频数据的采集通常由摄像头完成。摄像头捕获的原始视频数据通常是以YUV格式存储的。YUV是一种颜色编码系统,用于视频系统如电视和计算机图形。在这种格式中,Y是亮度分量(也称为灰度),而U和V是色度分量(代表颜色信息)。
在摄像头捕获图像时,图像的每个像素首先被转换为RGB格式,然后通常被转换为YUV格式。这是因为YUV格式有一些优点,例如它可以更有效地进行视频压缩,因为人眼对亮度信息(Y分量)比色度信息(U和V分量)更敏感,所以可以在保持视觉质量的同时减少色度信息的精度。
然而,尽管YUV是一种常见的视频数据格式,但并不是所有的摄像头都直接输出YUV数据。有些摄像头可能会输出其他格式的数据,例如RGB或者直接输出压缩后的数据(例如JPEG或H.264)。具体的输出格式取决于摄像头的硬件和驱动程序。
【FFmpeg 视频基本格式】深入理解FFmpeg:从YUV到PCM,解码到编码(三)https://developer.aliyun.com/article/1467278