音视频开发—使用FFmpeg从纯H264码流中提取图片 C语言实现

文章目录

    • 1.H264码流文件解码流程
      • 关键流程
      • 详细解码流程
      • 详细步骤解析
    • 2.JPEG编码流程
      • 详细编码流程
      • 详细步骤解析
    • 3.完整示例代码
    • 4.效果展示

从纯H.264码流中提取图片的过程包括解码JPEG编码两个主要步骤,以下是详细阐述

1.H264码流文件解码流程

关键流程

  • 查找编解码器

  • 初始化编解码器上下文—主要功能包括:

    存储编解码器参数:包括视频宽度、高度、像素格式、音频采样率、通道数等参数。

    存储编解码器状态:包括内部缓冲区、解码器状态、错误信息等。

    配置编解码器:允许用户通过设置上下文的属性来配置编解码器的行为。

    管理输入输出数据:负责管理编解码器的输入数据(如压缩视频流)和输出数据(如解码后的帧)。

  • 打开编解码器

  • 编解码

详细解码流程

在这里插入图片描述

详细步骤解析

初始化FFmpeg库

av_register_all();

打开输入文件

if ((ret = avformat_open_input(&format_ctx, input_filename, NULL, NULL)) < 0) {fprintf(stderr, "Could not open input file '%s'\n", input_filename);return ret;
}

找到输入文件的流信息

if ((ret = avformat_find_stream_info(format_ctx, NULL)) < 0) {fprintf(stderr, "Failed to retrieve input stream information\n");return ret;
}

找到视频流索引

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;
}

初始化解码器

codec = avcodec_find_decoder(format_ctx->streams[video_stream_index]->codecpar->codec_id);
if (!codec) {fprintf(stderr, "Could not find decoder\n");return -1;
}codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {fprintf(stderr, "Could not allocate video codec context\n");return -1;
}if ((ret = avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar)) < 0) {fprintf(stderr, "Could not copy codec parameters to context\n");return ret;
}if ((ret = avcodec_open2(codec_ctx, codec, NULL)) < 0) {fprintf(stderr, "Could not open codec\n");return ret;
}

分配帧和初始化SWS上下文

frame = av_frame_alloc();
if (!frame) {fprintf(stderr, "Could not allocate frame\n");return -1;
}sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);

读取帧并解码

while (av_read_frame(format_ctx, &packet) >= 0) {if (packet.stream_index == video_stream_index) {ret = avcodec_send_packet(codec_ctx, &packet);if (ret < 0) {fprintf(stderr, "Error sending packet for decoding\n");break;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0) {fprintf(stderr, "Error during decoding\n");return ret;}//处理帧,在这对Frame进行图片的编码}}av_packet_unref(&packet);
}

2.JPEG编码流程

与H264解码流程类似:均需要查找相关的编解码器,初始化上下文,打开编码器

详细编码流程

在这里插入图片描述

详细步骤解析

  1. 定义文件名

    • 使用snprintf生成保存JPEG图片的文件名,格式为frame<number>.jpg
    char filename[1024];
    snprintf(filename, sizeof(filename), "frame%d.jpg", frame_number);
    
  2. 寻找MJPEG编码器

    • 使用avcodec_find_encoder函数查找MJPEG编码器。如果找不到,记录错误日志并返回。
    AVCodec *jpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    if (!jpeg_codec) {av_log(NULL, AV_LOG_ERROR, "Could not find JPEG codec!\n");return;
    }
    
  3. 分配编码器上下文

    • 使用avcodec_alloc_context3函数为MJPEG编码器分配一个编码器上下文。如果分配失败,记录错误日志并返回。
    AVCodecContext *codec_ctx = avcodec_alloc_context3(jpeg_codec);
    if (!codec_ctx) {av_log(NULL, AV_LOG_ERROR, "Could not alloc codec_ctx !\n");return;
    }
    
  4. 设置编码器参数

    • 设置编码器的像素格式、视频高度、宽度和时间基准。
    codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;
    codec_ctx->height = frame->height;
    codec_ctx->width = frame->width;
    codec_ctx->time_base = (AVRational){1, 25};
    
  5. 打开编码器

    • 使用avcodec_open2函数打开编码器。如果打开失败,记录错误日志并返回。
    int ret = avcodec_open2(codec_ctx, jpeg_codec, NULL);
    if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Could not open jpeg codec !\n");avcodec_free_context(&codec_ctx);return;
    }
    
  6. 初始化数据包

    • 初始化一个AVPacket来存储编码后的数据。
    AVPacket packet;
    av_init_packet(&packet);
    packet.data = NULL;
    packet.size = 0;
    
  7. 发送帧到编码器

    • 使用avcodec_send_frame函数将帧发送到编码器。如果发送失败,记录错误日志并返回。
    ret = avcodec_send_frame(codec_ctx, frame);
    if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error sending frame to JPEG codec!\n");avcodec_free_context(&codec_ctx);return;
    }
    
  8. 接收编码后的数据包

    • 使用avcodec_receive_packet函数接收编码后的数据包。如果接收失败,记录错误日志并返回。
    ret = avcodec_receive_packet(codec_ctx, &packet);
    if (ret < 0) {av_log(NULL, AV_LOG_ERROR, "Error receiving packet from JPEG codec !\n");avcodec_free_context(&codec_ctx);return;
    }
    
  9. 写入文件

    • 打开一个文件,并将编码后的数据写入文件。如果文件打开失败,记录错误日志并返回。
    FILE *pic = fopen(filename, "wb");
    if (!pic) {av_log(NULL, AV_LOG_ERROR, "Could not open %s\n", filename);avcodec_free_context(&codec_ctx);av_packet_unref(&packet);return;
    }
    fwrite(packet.data, 1, packet.size, pic);
    fclose(pic);
    
  10. 记录成功日志

    • 记录成功写入文件的日志信息。
    av_log(NULL, AV_LOG_INFO, "write %s to jpeg success!\n", filename);
    
  11. 释放资源

    • 释放数据包和编码器上下文。
    av_packet_unref(&packet);
    avcodec_free_context(&codec_ctx);
    

3.完整示例代码

#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/log.h>/// @brief 编码为图片
/// @param frame
/// @param frame_number
void save_frame_as_jpeg(AVFrame *frame, int frame_number)
{int ret;char filename[1024];snprintf(filename, sizeof(filename), "frame%d.jpg", frame_number);// 寻找MJPEG编码器AVCodec *jpeg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);if (!jpeg_codec){av_log(NULL, AV_LOG_ERROR, "Could not find JPEG codec!\n");return;}AVCodecContext *codec_ctx = avcodec_alloc_context3(jpeg_codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Could not alloc codec_ctx !\n");return;}// 通过 AVCodecContext 设置jpeg codec 的参数codec_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P;codec_ctx->height = frame->height;codec_ctx->width = frame->width;codec_ctx->time_base = (AVRational){1, 25};// 打开编码器ret = avcodec_open2(codec_ctx, jpeg_codec, NULL);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Could not open jpeg codec !\n");return;}// 初始化数据包AVPacket packet;av_init_packet(&packet);packet.data = NULL;packet.size = 0;// 数据送入到编码器ret = avcodec_send_frame(codec_ctx, frame);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Error sending frame to JPEG codec!\n");avcodec_free_context(&codec_ctx);return;}//接收编码后的数据包ret = avcodec_receive_packet(codec_ctx,&packet);if(ret<0){av_log(NULL, AV_LOG_ERROR, "Error receiving packet from JPEG codec !\n");avcodec_free_context(&codec_ctx);return;}//写入到图片文件FILE *pic =fopen(filename,"wb");if(!pic){av_log(NULL, AV_LOG_ERROR, "Could not open %s\n",filename);avcodec_free_context(&codec_ctx);av_packet_unref(&packet);return;}fwrite(packet.data, 1, packet.size, pic);fclose(pic);av_log(NULL, AV_LOG_INFO, "write %s to jpeg success!\n");av_packet_unref(&packet);avcodec_free_context(&codec_ctx);}int main(int argc, char *argv[])
{av_log_set_level(AV_LOG_DEBUG);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG BEGIN.....\n");if (argc < 2){printf("Usage: %s <input file>\n", argv[0]);return -1;}const char *input_filename = argv[1]; // 输入H264码流文件名AVFormatContext *if_ctx = NULL;AVCodecContext *codec_ctx = NULL;AVCodec *codec = NULL;AVFrame *frame = NULL;AVPacket packet;struct SwsContext *sws_ctx = NULL; // 转换上下文int video_stream_index = -1;int ret = 0;int frame_count = 0;av_register_all(); // 初始化库ret = avformat_open_input(&if_ctx, input_filename, NULL, NULL);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Open input file failed! %s\n", av_err2str(ret));return ret;}// 寻找流信息ret = avformat_find_stream_info(if_ctx, NULL);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Find stream info  failed! %s\n", av_err2str(ret));return ret;}// 寻找视频流索引for (size_t i = 0; i < if_ctx->nb_streams; i++){if (if_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){video_stream_index = i;break;}}if (video_stream_index == -1){av_log(NULL, AV_LOG_ERROR, "Find video stream index  failed! \n");return ret;}// 寻找编码器codec = avcodec_find_decoder(if_ctx->streams[video_stream_index]->codecpar->codec_id);if (!codec){av_log(NULL, AV_LOG_ERROR, "Find codec decoder  failed! \n");return ret;}// 分配解码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx){av_log(NULL, AV_LOG_ERROR, "Alloc codec ctx  failed!\n");return ret;}ret = avcodec_parameters_to_context(codec_ctx, if_ctx->streams[video_stream_index]->codecpar);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Could not copy codec parameters to context %s\n", av_err2str(ret));return ret;}// 打开解码器ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Could not open codec %s\n", av_err2str(ret));return ret;}frame = av_frame_alloc();if (!frame){av_log(NULL, AV_LOG_ERROR, "Could not alloc frame\n");return -1;}sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL); // ;;用来转换帧的像素格式while (av_read_frame(if_ctx, &packet) >= 0){if (packet.stream_index == video_stream_index){ret = avcodec_send_packet(codec_ctx, &packet);if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Could not avcodec_send_packet %s\n", av_err2str(ret));break;}// 如果有B帧,可能有多个帧,因此使用whilewhile (ret >= 0){ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)break;else if (ret < 0){av_log(NULL, AV_LOG_ERROR, "Error during decoding %s\n", av_err2str(ret));break;}// 处理解码后的帧,保存图片save_frame_as_jpeg(frame,frame_count);frame_count++;       av_log(NULL, AV_LOG_DEBUG, "Decode success!\n");}}av_packet_unref(&packet);}av_frame_free(&frame);avcodec_free_context(&codec_ctx);avformat_close_input(&if_ctx);av_log(NULL, AV_LOG_INFO, "FFMPEG DEBUG LOG END.....\n");return 0;
}

4.效果展示

当读取一个H264文件之后,将会逐帧对H264数据进行解码,并且对解码后的关键帧进行编码,成功之后将会提取出所有的图片

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/45463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

敏捷开发笔记(第10章节)--Liskov原则(LSP)

目录 1&#xff1a;PDF上传链接 10.1 Liskov替换原则&#xff08;LSP&#xff09; 10.2 一个违反LSP的简单例子 10.6 启发式规则和习惯用法 10.7 结论 1&#xff1a;PDF上传链接 【免费】敏捷软件开发(原则模式与实践)资源-CSDN文库 OCP背后的主要机制是抽象(abstraction…

group 与查询字段

需求 每周周一&#xff0c;统计菜单在过去一周&#xff0c;点击次数&#xff0c;和点击人数&#xff08;同一个人访问多次按一次计算&#xff09; 表及数据 日志表 CREATE TABLE t_data_log ( id varchar(50) NOT NULL COMMENT 主键id, operation_object varchar(500) DE…

【D3.js in Action 3 精译】1.3 D3 视角下的数据可视化最佳实践(下)

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介 ✔️ 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知 1.2.1 HTML 与 DOM1.2.2 SVG - 可缩放矢量图形1.2.3 Canvas 与 WebGL1.2.4 CSS1.2.5 JavaScript1.2.6 Node 与 JavaScript 框架1.2.7 Observable 记事…

我的世界1.21多种服务端开服教程,原版/Forge/Fabric/Paper/Mohist...,Minecraft开服教程

Minecraft&#xff08;MC&#xff09;1.21版多种服务端开服教程&#xff0c;我的世界1.21服务器搭建教程&#xff0c;MC原版/Forge/Fabric/Paper/Mohist服务端搭建教程&#xff0c;我的世界MOD/插件服开服教程。 本教程使用 Linux系统MCSManager 面板来搭建Minecraft服务器。 …

人工智能行业应用-垃圾识别一

垃圾识别应用主要体现在AI图像垃圾识别技术上&#xff0c;这是一种基于人工智能和计算机视觉技术的图像处理技术&#xff0c;广泛应用于各个领域以提高垃圾处理的效率和准确性。 1、垃圾识别效果图 2 垃圾识别任务分析 综合利用Python语言、Qt开发模块&#xff0c;OpenCV开发模…

数据结构(Java):单链表面试OJ题

1、题一&#xff1a;获取链表倒数第k个节点 . - 力扣&#xff08;LeetCode&#xff09; 1.1 思路解析 此题我们使用双指针法求解。 首先&#xff0c;我们要知道&#xff0c;倒数的第k个节点&#xff0c;距离倒数第一个节点还需要移动k-1次。 1.那么我们可以定义出两个指针&a…

SQL去重的四种方法

去重是指&#xff1a;查询的时候, 不显示重复&#xff0c;并不是删除表中的重复项 数据表&#xff1a; 方法1&#xff1a;distinct去重 作用&#xff1a;只能一列去重&#xff0c;当distinct后跟大于1个参数时&#xff0c;他们之间的关系是&&(逻辑与)关系&#xff0c;…

00 Debian字符界面如何支持中文

作者&#xff1a;网络傅老师 特别提示&#xff1a;未经作者允许&#xff0c;不得转载任何内容。违者必究&#xff01; Debian字符界面如何支持中文 《傅老师Debian知识库系列之00》——原创 前言 傅老师Debian知识库特点&#xff1a; 1、拆解Debian实用技能&#xff1b; 2、…

ArcGIS获取21天免费教程,不用自己的邮箱

因为迟迟等不到3.1学习版本&#xff0c;但又很眼馋3.1的功能。 在群友的提醒下&#xff0c;官网免费注册21天试用&#xff0c;注册一次可以用21天&#xff0c;基本全部功能都可以使用。 &#xff08;也有群里的人用PayPal支付&#xff0c;然后设置离线许可&#xff0c;官网退…

Sentinel 学习笔记

Sentinel 学习笔记 作者&#xff1a;王珂 邮箱&#xff1a;49186456qq.com 文章目录 Sentinel 学习笔记[TOC] 前言一、基础概念二、Sentinel控制台2.1 安装控制台2.2 簇点链路2.3 请求限流2.4 线程隔离2.5 服务降级2.6 服务熔断 三、Sentinel客户端3.1 原始Jar包客户端3.2 Sp…

216.Mit6.S081-实验四-Traps

本实验探索如何使用陷阱实现系统调用。您将首先使用栈做一个热身练习&#xff0c;然后实现一个用户级陷阱处理的示例。 开始编码之前&#xff0c;请阅读xv6手册的第4章和相关源文件&#xff1a; kernel/trampoline.S&#xff1a;涉及从用户空间到内核空间再到内核空间的转换的…

【多线程】单例模式

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. 单例模式的初识2. 单例模式的含义3. 单例模式实现的两种方式3.1 饿汉模式3.2 懒汉模式3.2.1 懒汉模式(单线…

Redis的缓存雪崩,击穿,穿透的介绍

1.缓存雪崩 为保证缓存中的数据与数据库的数据一致,会给Redis里的数据设置一个过期时间,当缓存数据过期后,用户访问的数据如果不在缓存里,业务系统需要重新生成新的缓存,因为就会访问数据库,并将数据更新到Redis里,这样后续请求就可以直接命中缓存. 当大量缓存在同一时间过期或…

Nginx和Tomcat实现负载均衡群集部署应用

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作时间&…

减少CMOS模拟开关导通电阻引起的失真

1 简介 许多数据采集系统的在多通道间选择时需要使用模拟开关&#xff0c;相比同类的机械开关&#xff0c;半导体开关锁表现出的工作特性是迥然不同的。如&#xff1a;处在闭合位置的CMOS开关的电阻&#xff08;导通电阻 “Ron”&#xff09;会因输入电压的不同而改变。该特性通…

IMU的加速度补偿、祛除向心力

目录 1. 简介2. 仅有XY偏移的修正过程3. 3D修正过程 1. 简介 一般&#xff0c;我们期望用IMU测量某个Target坐标系的加速度、角速度信息&#xff0c;然而IMU的坐标系与Target 坐标系一般存在位姿关系&#xff0c;此时IMU测量的加速度不能直接代表Target左坐标系的加速度。比如…

python库(10):SpaCy库实现NLP处理

1 SpaCy简介 自然语言处理&#xff08;NLP&#xff09;是人工智能领域中一个重要的分支。它旨在使计算机能够理解、解释和生成人类语言。Python中的SpaCy库提供了丰富的功能和工具&#xff0c;SpaCy是一个开源的软件库&#xff0c;用于处理和操作自然语言文本&#xff0c;可以…

BM42:混合搜索的新基准 - Qdrant

在过去的 40 年里&#xff0c;BM25 一直是搜索引擎的标准。它是一种简单但功能强大的算法&#xff0c;已被许多搜索引擎使用&#xff0c;包括 Google、Bing 和 Yahoo。 虽然看起来向量搜索的出现会削弱其影响力&#xff0c;但效果并不明显。目前最先进的检索方法试图将 BM25 与…

python库(11):Box库简化字典和对象之间的转换

1Box库简介 Box是一个Python库&#xff0c;它提供了一种将数据封装在字典和列表中的方式&#xff0c;同时提供了一些额外的功能&#xff0c;比如数据验证、默认值设置等。这使得Box库非常适合用于配置管理、数据传输对象&#xff08;DTO&#xff09;的创建&#xff0c;以及任何…

sqlmap使用之-post注入、head注入(ua、cookie、referer)

1、post注入 1.1、方法一&#xff0c;通过保存数据包文件进行注入 bp抓包获取post数据 将数据保存到post.txt文件 加上-r指定数据文件 1.2、方法二、通过URL注入 D:\Python3.8.6\SQLmap>python sqlmap.py -u "http://localhost/login.php" --data "userna…