插件窝 干货文章 如何高效地将FFmpeg提取的视频帧直接写入内存进行处理?

如何高效地将FFmpeg提取的视频帧直接写入内存进行处理?

FFmpeg frame 内存 数据 930    来源:    2025-03-14

要将FFmpeg提取的视频帧直接写入内存进行处理,而不是保存到磁盘,可以使用FFmpeg的-f rawvideo选项将视频帧输出为原始视频数据,并通过管道或内存映射的方式将数据传递到内存中进行处理。以下是一个基本的实现思路:

1. 使用管道将FFmpeg输出传递到内存

你可以使用管道将FFmpeg的输出直接传递到内存中,然后在程序中读取这些数据进行处理。

示例代码(Python):

import subprocess
import numpy as np

# 设置FFmpeg命令
ffmpeg_command = [
    'ffmpeg',
    '-i', 'input.mp4',          # 输入视频文件
    '-f', 'rawvideo',           # 输出格式为原始视频数据
    '-pix_fmt', 'rgb24',        # 像素格式为RGB24
    '-'                         # 输出到标准输出
]

# 启动FFmpeg进程
process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# 读取视频帧数据
width = 1280  # 视频宽度
height = 720  # 视频高度
frame_size = width * height * 3  # 每帧的大小(RGB24格式)

while True:
    # 从标准输出读取一帧数据
    raw_frame = process.stdout.read(frame_size)
    if not raw_frame:
        break

    # 将原始数据转换为numpy数组
    frame = np.frombuffer(raw_frame, dtype=np.uint8).reshape((height, width, 3))

    # 在这里对帧进行处理
    # 例如:显示帧、分析帧内容等
    # ...

# 等待FFmpeg进程结束
process.wait()

2. 使用内存映射文件

如果你希望将FFmpeg的输出直接映射到内存中,可以使用内存映射文件(Memory-mapped file)。这种方法适用于需要将数据直接映射到内存中进行高效处理的场景。

示例代码(Python):

import subprocess
import numpy as np
import mmap

# 设置FFmpeg命令
ffmpeg_command = [
    'ffmpeg',
    '-i', 'input.mp4',          # 输入视频文件
    '-f', 'rawvideo',           # 输出格式为原始视频数据
    '-pix_fmt', 'rgb24',        # 像素格式为RGB24
    '-',                        # 输出到标准输出
    '>', 'output.raw'           # 将输出重定向到文件
]

# 启动FFmpeg进程
process = subprocess.Popen(ffmpeg_command, shell=True)

# 打开内存映射文件
with open('output.raw', 'r+b') as f:
    mmapped_file = mmap.mmap(f.fileno(), 0)

    # 读取视频帧数据
    width = 1280  # 视频宽度
    height = 720  # 视频高度
    frame_size = width * height * 3  # 每帧的大小(RGB24格式)

    offset = 0
    while offset + frame_size <= len(mmapped_file):
        # 从内存映射文件中读取一帧数据
        raw_frame = mmapped_file[offset:offset + frame_size]
        offset += frame_size

        # 将原始数据转换为numpy数组
        frame = np.frombuffer(raw_frame, dtype=np.uint8).reshape((height, width, 3))

        # 在这里对帧进行处理
        # 例如:显示帧、分析帧内容等
        # ...

    # 关闭内存映射文件
    mmapped_file.close()

3. 使用FFmpeg的API(libavformat/libavcodec)

如果你需要更底层的控制,可以直接使用FFmpeg的C API(libavformat/libavcodec)来读取视频帧并直接写入内存。这种方法需要编写C/C++代码,并且需要对FFmpeg的API有一定的了解。

示例代码(C/C++):

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>

int main() {
    av_register_all();

    AVFormatContext *format_ctx = avformat_alloc_context();
    if (avformat_open_input(&format_ctx, "input.mp4", NULL, NULL) != 0) {
        fprintf(stderr, "Could not open file\n");
        return -1;
    }

    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information\n");
        return -1;
    }

    int video_stream_index = -1;
    for (int i = 0; i < format_ctx->nb_streams; i++) {
        if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }

    if (video_stream_index == -1) {
        fprintf(stderr, "Could not find video stream\n");
        return -1;
    }

    AVCodecParameters *codec_par = format_ctx->streams[video_stream_index]->codecpar;
    AVCodec *codec = avcodec_find_decoder(codec_par->codec_id);
    if (!codec) {
        fprintf(stderr, "Unsupported codec\n");
        return -1;
    }

    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codec_ctx, codec_par);

    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        return -1;
    }

    AVFrame *frame = av_frame_alloc();
    AVPacket packet;

    while (av_read_frame(format_ctx, &packet) >= 0) {
        if (packet.stream_index == video_stream_index) {
            if (avcodec_send_packet(codec_ctx, &packet) == 0) {
                while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                    // 在这里处理帧数据
                    // frame->data[0], frame->linesize[0] 等包含了帧的像素数据
                }
            }
        }
        av_packet_unref(&packet);
    }

    av_frame_free(&frame);
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&format_ctx);

    return 0;
}

总结

  • 管道方法:适用于简单的场景,可以通过标准输入输出将数据传递到内存中。
  • 内存映射文件:适用于需要高效处理大量数据的场景。
  • FFmpeg API:适用于需要更底层控制和更高性能的场景。

根据你的具体需求选择合适的方法。