AVFrame的data数组数据结构详解

AVFrame的data数组数据结构详解

一、AVFrame结构概览

1.1 AVFrame定义(简化)

cpp 复制代码
typedef struct AVFrame {
    // 数据指针数组(核心存储)
    uint8_t *data[AV_NUM_DATA_POINTERS];  // AV_NUM_DATA_POINTERS = 8
    
    // 行大小(字节对齐后每行的字节数)
    int linesize[AV_NUM_DATA_POINTERS];
    
    // 图像/音频属性
    int width, height;          // 视频:宽高
    int format;                 // 像素格式或采样格式
    int nb_samples;             // 音频:采样点数
    int channels;               // 音频:声道数
    int64_t pts;                // 显示时间戳
    
    // ... 其他字段
} AVFrame;

二、data数组的布局(按媒体类型)

2.1 视频帧(YUV420P格式示例)

内存布局:
复制代码
data[0] → Y分量(亮度)的起始地址
data[1] → U分量(Cb,蓝色色差)的起始地址  
data[2] → V分量(Cr,红色色差)的起始地址
data[3..7] → NULL(未使用)
具体示例(352×288 YUV420P):
cpp 复制代码
// 分配后的内存状态
frame->format = AV_PIX_FMT_YUV420P;
frame->width = 352;
frame->height = 288;

// 各分量大小
size_y = linesize[0] * height;      // Y平面大小
size_u = linesize[1] * height/2;    // U平面大小(1/4分辨率)
size_v = linesize[2] * height/2;    // V平面大小(1/4分辨率)

// 实际数据布局
内存地址: data[0] → [Y00 Y01 ... Y(351,287)]  // 101,376字节
          data[1] → [U00 U01 ... U(175,143)]  // 25,344字节
          data[2] → [V00 V01 ... V(175,143)]  // 25,344字节

2.2 音频帧(FLTP格式示例)

内存布局(浮点平面格式):
复制代码
data[0] → 左声道数据起始地址(浮点数组)
data[1] → 右声道数据起始地址(浮点数组)  
data[2..7] → NULL(未使用)
具体示例(立体声FLTP):
cpp 复制代码
frame->format = AV_SAMPLE_FMT_FLTP;
frame->nb_samples = 1024;
frame->channels = 2;
frame->sample_rate = 44100;

// 内存布局
data[0] → [L0, L1, L2, ..., L1023]  // 1024个float,左声道
data[1] → [R0, R1, R2, ..., R1023]  // 1024个float,右声道

// 每个声道大小
size_per_channel = nb_samples * sizeof(float);  // 4096字节

三、不同像素格式的data布局

3.1 YUV420P(最常见的格式)

cpp 复制代码
// 分量分离存储(planar)
data[0] = Y平面 (width×height)
data[1] = U平面 (width/2 × height/2) 
data[2] = V平面 (width/2 × height/2)

// 访问示例:读取像素(100,50)的YUV值
int y = data[0][y * linesize[0] + x];                // x=100, y=50
int u = data[1][(y/2) * linesize[1] + (x/2)];        // x=50, y=25
int v = data[2][(y/2) * linesize[2] + (x/2)];        // x=50, y=25

3.2 YUV422P

cpp 复制代码
// 4:2:2采样,UV水平分辨率减半
data[0] = Y平面 (width×height)
data[1] = U平面 (width/2 × height)    // 注意:高度不减半
data[2] = V平面 (width/2 × height)

// 访问UV分量
int u = data[1][y * linesize[1] + (x/2)];

3.3 YUV444P

cpp 复制代码
// 4:4:4采样,UV全分辨率
data[0] = Y平面 (width×height)
data[1] = U平面 (width×height)        // 全分辨率
data[2] = V平面 (width×height)        // 全分辨率

// 访问UV分量
int u = data[1][y * linesize[1] + x];  // 坐标不减半

3.4 NV12(半平面YUV)

cpp 复制代码
// Y分量平面存储,UV交错存储
data[0] = Y平面 (width×height)
data[1] = UV交错平面 (width×height/2)

// 访问示例:像素(100,50)
int y = data[0][y * linesize[0] + x];
// UV交错:每2字节为一组[U,V]
int uv_index = (y/2) * linesize[1] + (x/2)*2;
int u = data[1][uv_index];      // 字节0: U分量
int v = data[1][uv_index + 1];  // 字节1: V分量

3.5 RGB24(打包格式)

cpp 复制代码
// RGB分量打包存储
data[0] = 所有像素的RGB数据

// 内存布局:[R0,G0,B0, R1,G1,B1, ...]
// linesize[0] = width * 3 + 填充字节

// 访问像素(x,y)
int pixel_index = y * linesize[0] + x * 3;
uint8_t r = data[0][pixel_index];      // 红色分量
uint8_t g = data[0][pixel_index + 1];  // 绿色分量  
uint8_t b = data[0][pixel_index + 2];  // 蓝色分量

四、不同音频采样格式的data布局

4.1 AV_SAMPLE_FMT_FLTP(浮点平面)

cpp 复制代码
// 每个声道单独存储为float数组
frame->format = AV_SAMPLE_FMT_FLTP;
frame->nb_samples = 1024;
frame->channels = 2;

// 访问左声道第i个采样
float left_sample = ((float*)data[0])[i];
// 访问右声道第i个采样  
float right_sample = ((float*)data[1])[i];

4.2 AV_SAMPLE_FMT_S16(有符号16位打包)

cpp 复制代码
// 所有声道交错打包存储
frame->format = AV_SAMPLE_FMT_S16;
frame->nb_samples = 1024;
frame->channels = 2;

// 数据布局:[L0,R0, L1,R1, L2,R2, ...]
// 访问第i个采样
int16_t* audio_data = (int16_t*)data[0];
int16_t left_sample = audio_data[i * 2];      // 左声道
int16_t right_sample = audio_data[i * 2 + 1]; // 右声道

4.3 AV_SAMPLE_FMT_S16P(有符号16位平面)

cpp 复制代码
// 每个声道单独存储为int16_t数组
frame->format = AV_SAMPLE_FMT_S16P;

// 访问左声道第i个采样
int16_t left_sample = ((int16_t*)data[0])[i];
// 访问右声道第i个采样
int16_t right_sample = ((int16_t*)data[1])[i];

五、linesize的重要性

5.1 linesize与width的关系

cpp 复制代码
// 对于352×288 YUV420P视频
frame->width = 352;
frame->height = 288;

// 实际的linesize可能为:
frame->linesize[0] = 352;  // 或对齐到384、512等
frame->linesize[1] = 176;  // U平面,可能对齐到192
frame->linesize[2] = 176;  // V平面,可能对齐到192

// 重要:linesize ≥ width(考虑对齐)

5.2 为什么需要linesize?

cpp 复制代码
// 错误访问(可能越界)
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        // ❌ 错误:假设每行就是width字节
        uint8_t pixel = data[0][y * width + x];
    }
}

// 正确访问(使用linesize)
for (int y = 0; y < height; y++) {
    for (int x = 0; x < width; x++) {
        // ✅ 正确:考虑内存对齐
        uint8_t pixel = data[0][y * linesize[0] + x];
    }
}

六、实际内存布局示例

6.1 YUV420P帧的内存映像

cpp 复制代码
// 假设352×288,linesize对齐到384
内存地址: 0x1000  [Y00 Y01 ... Y351 填充...填充]  // 384×288 = 110,592字节
         0x1B000 [U00 U01 ... U175 填充...填充]  // 192×144 = 27,648字节  
         0x21800 [V00 V01 ... V175 填充...填充]  // 192×144 = 27,648字节

// data指针指向
data[0] = 0x1000    // Y分量
data[1] = 0x1B000   // U分量
data[2] = 0x21800   // V分量

// linesize值
linesize[0] = 384   // 实际每行384字节(352有效+32填充)
linesize[1] = 192   // U平面每行192字节(176有效+16填充)
linesize[2] = 192   // V平面每行192字节(176有效+16填充)

6.2 音频FLTP帧的内存映像

cpp 复制代码
// 立体声,1024采样点
内存地址: 0x2000 [L0 L1 L2 ... L1023]  // 1024×4 = 4096字节
         0x3000 [R0 R1 R2 ... R1023]  // 1024×4 = 4096字节

// data指针指向
data[0] = 0x2000  // 左声道float数组
data[1] = 0x3000  // 右声道float数组

// linesize值(对于音频,linesize表示每个声道的数据大小)
linesize[0] = 4096  // 左声道数据大小
linesize[1] = 4096  // 右声道数据大小

七、data数组的初始化和管理

7.1 分配帧缓冲区

cpp 复制代码
// 方法1:自动分配
AVFrame* frame = av_frame_alloc();
frame->format = AV_PIX_FMT_YUV420P;
frame->width = 352;
frame->height = 288;
av_frame_get_buffer(frame, 32);  // 32字节对齐

// 方法2:手动设置外部缓冲区
uint8_t* yuv_buffer = malloc(total_size);
frame->data[0] = yuv_buffer;                    // Y分量
frame->data[1] = yuv_buffer + y_size;           // U分量
frame->data[2] = yuv_buffer + y_size + u_size;  // V分量
frame->linesize[0] = width;
frame->linesize[1] = width/2;
// 需要设置引用计数避免提前释放

7.2 检查帧是否可写

cpp 复制代码
// 重要:编码器可能持有帧的引用
if (!av_frame_is_writable(frame)) {
    // 需要复制帧或等待引用释放
    AVFrame* tmp = av_frame_clone(frame);
    av_frame_unref(frame);
    av_frame_move_ref(frame, tmp);
    av_frame_free(&tmp);
}

八、常见操作示例

8.1 遍历视频帧所有像素

cpp 复制代码
void process_yuv_frame(AVFrame* frame) {
    int width = frame->width;
    int height = frame->height;
    
    // 处理Y分量
    for (int y = 0; y < height; y++) {
        uint8_t* y_line = frame->data[0] + y * frame->linesize[0];
        for (int x = 0; x < width; x++) {
            y_line[x] = process_pixel(y_line[x]);
        }
    }
    
    // 处理UV分量(分辨率减半)
    int uv_height = height / 2;
    int uv_width = width / 2;
    
    for (int y = 0; y < uv_height; y++) {
        uint8_t* u_line = frame->data[1] + y * frame->linesize[1];
        uint8_t* v_line = frame->data[2] + y * frame->linesize[2];
        for (int x = 0; x < uv_width; x++) {
            u_line[x] = process_chroma(u_line[x]);
            v_line[x] = process_chroma(v_line[x]);
        }
    }
}

8.2 音频帧处理

cpp 复制代码
void process_audio_frame(AVFrame* frame) {
    int nb_samples = frame->nb_samples;
    int channels = frame->channels;
    
    if (frame->format == AV_SAMPLE_FMT_FLTP) {
        // 平面格式:每个声道单独处理
        for (int ch = 0; ch < channels; ch++) {
            float* channel_data = (float*)frame->data[ch];
            for (int i = 0; i < nb_samples; i++) {
                channel_data[i] = process_sample(channel_data[i]);
            }
        }
    } else if (frame->format == AV_SAMPLE_FMT_S16) {
        // 打包格式:声道交错
        int16_t* audio_data = (int16_t*)frame->data[0];
        for (int i = 0; i < nb_samples; i++) {
            for (int ch = 0; ch < channels; ch++) {
                int index = i * channels + ch;
                audio_data[index] = process_sample(audio_data[index]);
            }
        }
    }
}

九、总结

AVFrame的data数组设计体现了FFmpeg的灵活性和高效性

关键特点:

  1. 多平面存储:支持YUV/RGB的平面和打包格式
  2. 内存对齐:通过linesize支持硬件优化
  3. 格式无关:统一接口处理各种像素/采样格式
  4. 零拷贝:支持外部缓冲区,避免数据复制

使用原则:

  1. 永远使用linesize而非width进行内存访问
  2. 检查帧可写性后再修改数据
  3. 理解格式布局才能正确访问数据
  4. 利用FFmpeg工具函数av_image_fill_arrays()
相关推荐
optimistic_chen13 分钟前
【Redis 系列】常用数据结构---Hash类型
linux·数据结构·redis·分布式·哈希算法
儒道易行37 分钟前
平凡的2025年终总结
网络·安全·web安全
五阿哥永琪1 小时前
Redis的常用数据结构
数据结构·数据库·redis
乾元1 小时前
数据中心流量工程(TE)优化:当 AI 成为解决“维度诅咒”的唯一操纵杆
运维·服务器·网络·人工智能·架构·自动化
Sheep Shaun1 小时前
STL中的map和set:红黑树的优雅应用
开发语言·数据结构·c++·后端·c#
Andy工程师1 小时前
网络响应码(HTTP 状态码)和解析方法
网络·网络协议·http
H_BB1 小时前
leetcode160:相交链表
数据结构·算法·链表
浅川.251 小时前
STL专项:queue 队列
数据结构·stl·queue
heartbeat..2 小时前
JavaWeb 核心:HttpServletRequest 请求行、请求头、请求参数完整梳理
java·网络·web·request
小馬佩德罗2 小时前
如何将x264 x265的动态库编译入Linux系统中的FFmpeg源码 - x264库编译
linux·ffmpeg·x264