FFmpeg集成二维码处理:使用qrencode和quirc

FFmpeg集成二维码处理:使用qrencode和quirc

概述

本文将介绍如何在FFmpeg中集成qrencode(二维码生成)和quirc(二维码识别)库,实现视频画面中二维码的读写功能。这种技术可应用于视频水印、互动视频、信息隐藏等多种场景。

系统架构

生成二维码
识别二维码
输入视频
FFmpeg处理管道
操作模式
qrencode生成
quirc识别
叠加到视频帧
提取二维码数据
输出带二维码视频
输出识别结果

环境准备

安装依赖库

# 安装qrencode (二维码生成)
sudo apt-get install libqrencode-dev

# 安装quirc (二维码识别)
git clone https://github.com/dlbeer/quirc.git
cd quirc
make
sudo make install

# 安装FFmpeg开发依赖
sudo apt-get install ffmpeg libavcodec-dev libavformat-dev libswscale-dev

二维码生成实现

创建qrencode滤镜

在FFmpeg中创建vf_qrcodegen.c滤镜:

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

typedef struct {
    const AVClass *class;
    char *text;          // 二维码内容
    int x, y;            // 位置坐标
    int size;            // 二维码尺寸
    int margin;          // 边距
    int version;         // 二维码版本
    int level;           // 容错级别
    int update_interval; // 更新间隔(帧数)
    
    // 内部状态
    int64_t frame_count;
    AVFrame *qr_frame;
    struct SwsContext *sws_ctx;
} QRCodeGenContext;

#define OFFSET(x) offsetof(QRCodeGenContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM

static const AVOption qrcodegen_options[] = {
    {"text", "QR code content", OFFSET(text), AV_OPT_TYPE_STRING, {.str = "FFmpeg QR Code"}, 0, 0, FLAGS},
    {"x", "X position", OFFSET(x), AV_OPT_TYPE_INT, {.i64 = 20}, 0, INT_MAX, FLAGS},
    {"y", "Y position", OFFSET(y), AV_OPT_TYPE_INT, {.i64 = 20}, 0, INT_MAX, FLAGS},
    {"size", "QR code size", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 100}, 20, 500, FLAGS},
    {"margin", "Margin size", OFFSET(margin), AV_OPT_TYPE_INT, {.i64 = 2}, 0, 10, FLAGS},
    {"version", "QR version (1-40)", OFFSET(version), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 40, FLAGS},
    {"level", "Error correction level (0=L, 1=M, 2=Q, 3=H)", OFFSET(level), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 3, FLAGS},
    {"update", "Update interval (frames)", OFFSET(update_interval), AV_OPT_TYPE_INT, {.i64 = 30}, 1, 1000, FLAGS},
    {NULL}
};

static int generate_qrcode(QRCodeGenContext *s)
{
    QRcode *qrcode;
    uint8_t *p;
    int x, y, qr_size;
    
    // 释放之前的二维码帧
    av_frame_free(&s->qr_frame);
    
    // 生成二维码
    QRecLevel level = QR_ECLEVEL_M;
    if (s->level == 0) level = QR_ECLEVEL_L;
    else if (s->level == 2) level = QR_ECLEVEL_Q;
    else if (s->level == 3) level = QR_ECLEVEL_H;
    
    qrcode = QRcode_encodeString(s->text, s->version, level, QR_MODE_8, 1);
    if (!qrcode) {
        av_log(s, AV_LOG_ERROR, "Failed to generate QR code\n");
        return AVERROR(ENOMEM);
    }
    
    // 创建二维码帧
    qr_size = qrcode->width + 2 * s->margin;
    s->qr_frame = av_frame_alloc();
    if (!s->qr_frame) {
        QRcode_free(qrcode);
        return AVERROR(ENOMEM);
    }
    
    s->qr_frame->format = AV_PIX_FMT_GRAY8;
    s->qr_frame->width = qr_size;
    s->qr_frame->height = qr_size;
    if (av_frame_get_buffer(s->qr_frame, 32) < 0) {
        av_frame_free(&s->qr_frame);
        QRcode_free(qrcode);
        return AVERROR(ENOMEM);
    }
    
    // 填充边距
    memset(s->qr_frame->data[0], 255, s->qr_frame->linesize[0] * qr_size);
    
    // 复制二维码数据
    p = s->qr_frame->data[0] + s->margin * s->qr_frame->linesize[0] + s->margin;
    for (y = 0; y < qrcode->width; y++) {
        memcpy(p, qrcode->data + y * qrcode->width, qrcode->width);
        p += s->qr_frame->linesize[0];
    }
    
    QRcode_free(qrcode);
    return 0;
}

static int config_output(AVFilterLink *outlink)
{
    AVFilterContext *ctx = outlink->src;
    QRCodeGenContext *s = ctx->priv;
    
    // 生成初始二维码
    return generate_qrcode(s);
}

static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
{
    AVFilterContext *ctx = inlink->dst;
    QRCodeGenContext *s = ctx->priv;
    int ret;
    
    // 定期更新二维码
    s->frame_count++;
    if (s->frame_count % s->update_interval == 0) {
        if ((ret = generate_qrcode(s)) < 0)
            return ret;
    }
    
    // 将二维码叠加到视频帧
    if (s->qr_frame) {
        struct SwsContext *sws_ctx = sws_getContext(
            s->qr_frame->width, s->qr_frame->height, AV_PIX_FMT_GRAY8,
            s->qr_frame->width, s->qr_frame->height, frame->format,
            SWS_BILINEAR, NULL, NULL, NULL);
        
        if (sws_ctx) {
            uint8_t *src[] = {s->qr_frame->data[0]};
            int src_stride[] = {s->qr_frame->linesize[0]};
            
            // 计算目标位置
            int dst_x = s->x;
            int dst_y = s->y;
            int dst_w = s->size;
            int dst_h = s->size;
            
            // 创建临时区域
            uint8_t *dst[] = {frame->data[0] + dst_y * frame->linesize[0] + dst_x * 3};
            int dst_stride[] = {frame->linesize[0]};
            
            // 缩放并叠加
            sws_scale(sws_ctx, src, src_stride, 0, s->qr_frame->height,
                      dst, dst_stride);
            
            sws_freeContext(sws_ctx);
        }
    }
    
    return ff_filter_frame(ctx->outputs[0], frame);
}

// 注册滤镜等代码省略...

二维码识别实现

创建quirc滤镜

在FFmpeg中创建vf_qrcoderead.c滤镜:

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

typedef struct {
    const AVClass *class;
    int scan_interval;      // 扫描间隔(帧数)
    int draw_detection;     // 是否绘制检测框
    int output_file;         // 输出文件路径
    
    // 内部状态
    int64_t frame_count;
    struct quirc *quirc;
    FILE *output_fp;
} QRCodeReadContext;

#define OFFSET(x) offsetof(QRCodeReadContext, x)
#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM

static const AVOption qrcoderead_options[] = {
    {"interval", "Scan interval (frames)", OFFSET(scan_interval), AV_OPT_TYPE_INT, {.i64 = 10}, 1, 1000, FLAGS},
    {"draw", "Draw detection boxes", OFFSET(draw_detection), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS},
    {"output", "Output file path", OFFSET(output_file), AV_OPT_TYPE_STRING, {.str = "qrcodes.txt"}, 0, 0, FLAGS},
    {NULL}
};

static int config_input(AVFilterLink *inlink)
{
    AVFilterContext *ctx = inlink->dst;
    QRCodeReadContext *s = ctx->priv;
    
    // 初始化quirc
    s->quirc = quirc_new();
    if (!s->quirc) {
        av_log(ctx, AV_LOG_ERROR, "Failed to create quirc context\n");
        return AVERROR(ENOMEM);
    }
    
    // 打开输出文件
    s->output_fp = fopen(s->output_file, "a");
    if (!s->output_fp) {
        av_log(ctx, AV_LOG_WARNING, "Could not open output file: %s\n", s->output_file);
    }
    
    return 0;
}

static int process_frame(QRCodeReadContext *s, AVFrame *frame)
{
    int width = frame->width;
    int height = frame->height;
    struct quirc *q = s->quirc;
    uint8_t *image;
    int count, i;
    
    // 调整quirc缓冲区大小
    if (quirc_resize(q, width, height) < 0) {
        av_log(s, AV_LOG_ERROR, "Failed to resize quirc buffer\n");
        return AVERROR(ENOMEM);
    }
    
    // 获取图像缓冲区
    image = quirc_begin(q, &width, &height);
    
    // 转换为灰度图
    struct SwsContext *sws_ctx = sws_getContext(
        frame->width, frame->height, frame->format,
        width, height, AV_PIX_FMT_GRAY8,
        SWS_BILINEAR, NULL, NULL, NULL);
    
    if (sws_ctx) {
        const uint8_t *src[] = {frame->data[0]};
        int src_stride[] = {frame->linesize[0]};
        uint8_t *dst[] = {image};
        int dst_stride[] = {width};
        
        sws_scale(sws_ctx, src, src_stride, 0, frame->height, dst, dst_stride);
        sws_freeContext(sws_ctx);
    } else {
        // 简单灰度转换
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                uint8_t *pixel = frame->data[0] + y * frame->linesize[0] + x * 3;
                image[y * width + x] = (pixel[0] + pixel[1] + pixel[2]) / 3;
            }
        }
    }
    
    quirc_end(q);
    
    // 检测二维码
    count = quirc_count(q);
    for (i = 0; i < count; i++) {
        struct quirc_code code;
        struct quirc_data data;
        quirc_decode_error_t err;
        
        quirc_extract(q, i, &code);
        err = quirc_decode(&code, &data);
        
        if (err == QUIRC_SUCCESS) {
            char timestamp[64];
            struct timeval tv;
            gettimeofday(&tv, NULL);
            snprintf(timestamp, sizeof(timestamp), "%ld.%06ld", tv.tv_sec, tv.tv_usec);
            
            // 输出到日志
            av_log(s, AV_LOG_INFO, "QR Code detected: %s\n", data.payload);
            
            // 输出到文件
            if (s->output_fp) {
                fprintf(s->output_fp, "[%s] Frame: %"PRId64" | QR: %s\n", 
                        timestamp, s->frame_count, data.payload);
                fflush(s->output_fp);
            }
            
            // 绘制检测框(可选)
            if (s->draw_detection) {
                draw_detection_box(frame, &code);
            }
        }
    }
    
    return count;
}

static void draw_detection_box(AVFrame *frame, struct quirc_code *code)
{
    int i;
    const int thickness = 2;
    const uint8_t color[3] = {0, 255, 0}; // 绿色
    
    // 绘制四个角点
    for (i = 0; i < 4; i++) {
        int x0 = code->corners[i].x;
        int y0 = code->corners[i].y;
        int x1 = code->corners[(i+1)%4].x;
        int y1 = code->corners[(i+1)%4].y;
        
        // 使用Bresenham算法绘制直线
        draw_line(frame, x0, y0, x1, y1, color, thickness);
    }
}

// 注册滤镜等代码省略...

编译与集成

修改FFmpeg配置

configure中添加:

# 检测qrencode
enabled libqrencode && require_pkg_config libqrencode qrencode.h qrcode_encodeString

# 检测quirc
quirc_libs="-lquirc"
check_lib quirc.h quirc_new -lquirc || disable quirc
enabled quirc && require libquirc $quirc_libs

注册新滤镜

libavfilter/allfilters.c中添加:

extern AVFilter ff_vf_qrcodegen;
extern AVFilter ff_vf_qrcoderead;

// 在滤镜列表中添加
static const AVFilter *filter_list[] = {
    // ... 其他滤镜
    &ff_vf_qrcodegen,
    &ff_vf_qrcoderead,
    NULL
};

编译FFmpeg

./configure \
  --prefix=/usr/local \
  --enable-gpl \
  --enable-libqrencode \
  --enable-libquirc \
  --enable-shared

make -j$(nproc)
sudo make install

使用示例

1. 生成带二维码的视频

# 添加静态二维码
ffmpeg -i input.mp4 -vf "qrcodegen=text='Hello FFmpeg':x=20:y=20:size=100" output.mp4

# 添加动态二维码(每30帧更新)
ffmpeg -i input.mp4 -vf \
"qrcodegen=text='Frame %{frame_num}':x=20:y=20:size=100:update=30" \
output.mp4

# 使用二维码作为水印
ffmpeg -i input.mp4 -i logo.png \
-filter_complex \
"[0:v][1:v]overlay=W-w-10:10,qrcodegen=text='Protected Content':x=10:y=H-h-10:size=80" \
output.mp4

2. 识别视频中的二维码

# 基本识别
ffmpeg -i input.mp4 -vf "qrcoderead" -f null -

# 保存识别结果到文件
ffmpeg -i input.mp4 -vf "qrcoderead=output=qrcodes.txt" -f null -

# 带检测框显示
ffmpeg -i input.mp4 -vf "qrcoderead=draw=1" output.mp4

3. 高级应用:视频元数据嵌入

# 嵌入元数据到二维码
ffmpeg -i input.mp4 -vf \
"qrcodegen=text='title=My Video;author=FFmpeg;date=$(date +%Y-%m-%d)':update=1" \
output.mp4

# 提取元数据
ffmpeg -i output.mp4 -vf "qrcoderead" -f null - 2> metadata.txt

性能优化技巧

1. 二维码生成优化

// 缓存二维码图像
static int need_update(QRCodeGenContext *s)
{
    // 仅在内容变化或间隔到达时更新
    static char last_text[1024] = {0};
    if (strcmp(last_text, s->text) || 
        s->frame_count % s->update_interval == 0) {
        strncpy(last_text, s->text, sizeof(last_text)-1);
        return 1;
    }
    return 0;
}

2. 二维码识别优化

// ROI(感兴趣区域)检测
static void detect_roi(AVFrame *frame, int *roi_x, int *roi_y, int *roi_w, int *roi_h)
{
    // 实现基于运动检测或颜色特征的ROI提取
    // 可减少处理区域,提高识别速度
}

// 在process_frame中
int roi_x, roi_y, roi_w, roi_h;
detect_roi(frame, &roi_x, &roi_y, &roi_w, &roi_h);

// 仅处理ROI区域
if (quirc_resize(q, roi_w, roi_h) < 0) {
    // 错误处理
}

3. 多线程处理

// 在滤镜初始化中启用多线程
s->thread_count = av_cpu_count();
s->threads = av_malloc_array(s->thread_count, sizeof(pthread_t));

// 分割帧为多个区域并行处理
for (int i = 0; i < s->thread_count; i++) {
    int start_y = i * height / s->thread_count;
    int end_y = (i+1) * height / s->thread_count;
    pthread_create(&s->threads[i], NULL, process_region, 
                  (struct thread_data){s, frame, start_y, end_y});
}

应用场景

1. 视频版权保护

原始视频
嵌入版权二维码
分发视频
盗版检测
提取二维码溯源

2. 互动视频系统

1. 视频中显示问题二维码
2. 用户扫描二维码回答问题
3. 系统根据答案跳转不同剧情

3. 视频信息增强

# 在视频中嵌入产品信息二维码
ffmpeg -i product.mp4 -vf \
"qrcodegen=text='Product ID:12345;Price:$99.99;URL=...':x=0.9*W:y=0.8*H:size=0.2*min(W,H)" \
output.mp4

故障排除

常见问题及解决方案

问题可能原因解决方案
二维码无法识别分辨率太低增加二维码尺寸,减少边距
识别率低视频压缩失真提高视频码率,使用容错级别H
生成速度慢频繁更新大尺寸二维码增加更新间隔,减小尺寸
位置偏移视频分辨率变化使用相对位置(W-w-10)
内存泄漏未释放资源检查所有分配的资源是否释放

调试技巧

# 启用详细日志
ffmpeg -v debug -i input.mp4 -vf "qrcodegen" output.mp4

# 导出二维码图像
ffmpeg -i input.mp4 -vf "select=eq(n\,10)" -vframes 1 qr_frame.png

总结

通过集成qrencode和quirc库,FFmpeg可以实现强大的二维码处理能力:

  1. 二维码生成:可控制大小、位置、内容和更新频率
  2. 二维码识别:高效检测并提取视频中的二维码信息
  3. 性能优化:支持ROI检测、多线程处理和智能更新
  4. 应用广泛:版权保护、互动视频、信息增强等场景

本文提供的实现方案可以直接集成到FFmpeg中,也可作为独立模块用于其他视频处理系统。实际应用中,可根据需求调整二维码参数和识别策略,平衡性能和效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值