使用ffmpeg的c++库读取视频流和其中的SEI数据

2024-09-02 17:04

本文主要是介绍使用ffmpeg的c++库读取视频流和其中的SEI数据,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用ffmpeg读取视频流和其中的SEI数据(未完待续)

FFmpeg是一个多媒体软件框架,支持多种新旧视频编码格式,提供解码、编码、 转码、多路复用、解复用、流式传输、过滤和播放等功能。其包含:

  1. C++库libavcodec、libavutil、libavformat、libavfilter、libavdevice、 libswscalelibswresample
  2. 基于库构建的命令行工具ffmpeg \ ffplayer \ ffprobe

关于ffmpeg库:

  • libavutil库包含了一系列工具函数用于简化编程,这些工具包括随机数生成器、数据结构、数学例程、核心多媒体实用程序等等。
  • libavcodec库包含音频/视频编解码器。
  • libavformat用于多媒体容器格式的复用与解复用功能。
  • libavdevice是一个包含输入和输出设备的库,用于从设备抓取和渲染到设备。支持许多常见的多媒体输入/输出软件框架,包括Video4Linux、Video4Linux2、VfW和ALSA。
  • libavfilter是一个包含媒体过滤器的库。
  • libswscale是一个执行高度优化的图像缩放和颜色空间/像素格式转换操作的库。
  • libswresample是一个执行高度优化的音频重采样、重匹配和样本格式转换操作的库。

安装ffmpeg c++库

推荐使用vcpkg编译、安装:

vcpkg install ffmpeg:x64-windows

使用ffmpeg读取视频流的示例代码

extern "C"
{
#include <libavcodec/avcodec.h>//avcodec:编解码(最重要的库)
#include <libavformat/avformat.h>//avformat:封装格式处理
#include <libswscale/swscale.h>//swscale:视频像素数据格式转换
#include <libavdevice/avdevice.h>//avdevice:各种设备的输入输出
#include <libavutil/avutil.h>//avutil:工具库(大部分库都需要这个库的支持)
#include <libavutil/imgutils.h>
}
#include<jpeglib.h>
#include<iostream>
#include<fstream>
#include<string>// Function to save a frame as a JPG image
void SaveFrameAsJpg(const std::string& strDir, AVFrame* pFrameRGB, int width, int height, int frameIndex) {char fileName[1024];snprintf(fileName, sizeof(fileName), "frame%d.jpg", frameIndex);FILE* outFile = fopen((strDir+fileName).c_str(), "wb");if (!outFile) {std::cerr << "Could not open file for writing: " << fileName << std::endl;return;}struct jpeg_compress_struct cinfo;struct jpeg_error_mgr jerr;cinfo.err = jpeg_std_error(&jerr);jpeg_create_compress(&cinfo);jpeg_stdio_dest(&cinfo, outFile);cinfo.image_width = width;cinfo.image_height = height;cinfo.input_components = 3;  // RGB has 3 componentscinfo.in_color_space = JCS_RGB;jpeg_set_defaults(&cinfo);jpeg_set_quality(&cinfo, 100, TRUE);jpeg_start_compress(&cinfo, TRUE);JSAMPROW row_pointer[1];while (cinfo.next_scanline < cinfo.image_height) {row_pointer[0] = &pFrameRGB->data[0][cinfo.next_scanline * pFrameRGB->linesize[0]];jpeg_write_scanlines(&cinfo, row_pointer, 1);}jpeg_finish_compress(&cinfo);jpeg_destroy_compress(&cinfo);fclose(outFile);
}int main()
{std::string strStreamAddress="Your video stream address.";AVFormatContext* pFormatContext = avformat_alloc_context();if (!pFormatContext) {std::cerr << "Could not allocate memory for Format Context" << std::endl;return -1;}int ret = avformat_open_input(&pFormatContext, strStreamAddress.c_str(), nullptr, nullptr);if (ret != 0){//获取异常信息        char* error_info = new char[32];av_strerror(ret, error_info, 1024);std::cout<<"异常信息 "<<ret<<" "<<error_info << std::endl;delete[] error_info;return -1;}std::cout << "打开的视频文件格式:" << pFormatContext->iformat->name<<std::endl;std::cout << "查找视频流信息..." << std::endl;//参数一:封装格式上下文->AVFormatContext//参数二:配置//返回值:0>=返回OK,否则失败ret = avformat_find_stream_info(pFormatContext, NULL);if (ret < 0){//获取异常信息        char* error_info = new char[32];av_strerror(ret, error_info, 1024);std::cout<<"异常信息 "<<error_info << std::endl;}std::cout << "查找解码器...\n" << "流信息" << pFormatContext->nb_streams << std::endl;//第一点:获取当前解码器是属于什么类型解码器->找到了视频流//音频解码器、视频解码器、字幕解码器等等...//获取视频解码器流引用int av_stream_index = -1;for (int i = 0; i < pFormatContext->nb_streams; ++i){//循环遍历每个流,例如视频流、音频流、字幕流等等...if (pFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){//找到视频流            av_stream_index = i;break;}}if (av_stream_index == -1)  std::cout<<"没有找到视频流";AVCodecParameters* pCodecParameters = pFormatContext->streams[av_stream_index]->codecpar;const AVCodec* pCodec = avcodec_find_decoder(pCodecParameters->codec_id);if (!pCodec) {std::cerr << "Unsupported codec!" << std::endl;return -1;}AVCodecContext* pCodecContext = avcodec_alloc_context3(pCodec);if (!pCodecContext) {std::cerr << "Could not allocate memory for Codec Context" << std::endl;return -1;}if (avcodec_parameters_to_context(pCodecContext, pCodecParameters) < 0) {std::cerr << "Could not copy codec parameters to codec context" << std::endl;return -1;}if (avcodec_open2(pCodecContext, pCodec, nullptr) < 0) {std::cerr << "Could not open codec" << std::endl;return -1;}AVFrame* pFrame = av_frame_alloc();AVFrame* pFrameRGB = av_frame_alloc();if (!pFrame || !pFrameRGB) {std::cerr << "Could not allocate frame" << std::endl;return -1;}int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1);uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, buffer, AV_PIX_FMT_RGB24, pCodecContext->width, pCodecContext->height, 1);struct SwsContext* sws_ctx = sws_getContext(pCodecContext->width,pCodecContext->height,pCodecContext->pix_fmt,pCodecContext->width,pCodecContext->height,AV_PIX_FMT_RGB24, SWS_BILINEAR,nullptr, nullptr, nullptr);AVCodecParserContext* parser = av_parser_init(pCodec->id);if (!parser) {std::cerr << "Could not initialize parser" << std::endl;return -1;}AVPacket packet;int frameIndex = 0;while (av_read_frame(pFormatContext, &packet) >= 0){if (packet.stream_index == av_stream_index) {ret = avcodec_send_packet(pCodecContext, &packet);if (ret < 0) {std::cerr << "Error sending packet for decoding" << std::endl;continue;}while (ret >= 0) {ret = avcodec_receive_frame(pCodecContext, pFrame);if (frameIndex % 64 == 1/*&& pFrame->key_frame*/)// 每64帧保留一帧{std::string strInfo;//extract_sei_info(packet.data, packet.size, strInfo);//cout<<strInfo;if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;else if (ret < 0) {std::cerr << "Error during decoding" << std::endl;return -1;}sws_scale(sws_ctx,(uint8_t const* const*)pFrame->data,pFrame->linesize,0,pCodecContext->height,pFrameRGB->data,pFrameRGB->linesize);SaveFrameAsJpg(strOutputDir, pFrameRGB, pCodecContext->width, pCodecContext->height, frameIndex);}frameIndex++;}}av_packet_unref(&packet);}av_free(buffer);av_frame_free(&pFrame);av_frame_free(&pFrameRGB);avcodec_free_context(&pCodecContext);avformat_close_input(&pFormatContext);return 0;
}

其中,SaveFrameAsJpg函数用于将一帧图像数据保存为jpg格式。

使用ffmpeg读取视频流中的SEI数据

SEI是什么

SEI是Supplementary Enhanced Information的简写,意为补充增强信息,是视频流中的可选附加信息,常用于在视频流中插入字幕、时间戳等额外信息。解码视频流的同时将SEI信息解码出来,可以实现多种同步的应用。

SEI在数据流中如何传输?

H.264标准中,音视频流数据必须以NALU(Network Abstraction Layer Unit)的形式传输,SEI数据也不例外,必须被包装在NALU中。顾名思义,NALU是网络抽象层数据传输的单元,其结构为:NALU头+ RBSP
RBSP(Raw Byte Sequence Payload)是原始字节序列负荷,也即数据内容。

在一段NALU之前,必以0x000001或者0x00000001作起始码,紧跟着的8bit便是一个NALU 头。
在H.264标准中,NALU头的8个bit的含义如下:

|0|1|2|3|4|5|6|7|
|F|NRI| Type |

其中,后5个bit表示当前NALU所包含数据的类型,若后5bit的值为0x06,则说明该数据段为SEI信息单元;若值为0x05,则说明该数据段为视频的IDR帧……
总之,软件根据Type信息知晓当前NALU包含的是什么数据,从而选择对应的数据解析方式。

读取SEI的示例代码

根据上面的介绍,要读取SEI数据,首先需要解析出Type为6的NALU单元。然后,读取
SEI中写入的信息是用户自定义的,根据视频流数据发布者提供的信息,只要找到SEI数据开始的位置,即可解析。
下面给出解析SEI数据的两个函数:


// 从数据流找到包含SEI数据的NALU,然后调用parse_sei_data解析NALU
void extract_sei_info(const uint8_t* data, int size, std::string& sei_geom_info) {// Check if the packet is an H.264 NAL unitif (data && size > 0) {int i = 0;unsigned char* pstr = nullptr;int nLen = 0;while (i < size - 4) {uint8_t nal_type = 0;if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x00 && data[i + 3] == 0x01) {nal_type = data[i + 4] & 0x1F;if (nal_type == 6 && (data[i + 5] & 0x1F) == 5) {std::cout << "Found SEI NAL unit" << std::endl;parse_sei_data(data + i + 5, size - (i + 5), &pstr, nLen);break;}}else if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01) {nal_type = data[i + 3] & 0x1F;if (nal_type == 6 && (data[i + 5] & 0x1F) == 5) {std::cout << "Found SEI NAL unit" << std::endl;parse_sei_data(data + i + 4, size - (i + 4), &pstr, nLen);break;}}elsei++;}if(pstr!=nullptr)sei_geom_info=pstr;}return;
}// 提取SEI中的数据(数据指针*pParsedData、数据大小nSize)
void parse_sei_data(const uint8_t* data, int size, uint8_t** pParsedData, int& nSize) {// Simplified SEI parsing logicint offset = 0;while (offset < size) {uint8_t payload_type = 0;uint8_t payload_size = 0;// Read payload typewhile (offset < size && data[offset] == 0xFF) {payload_type += 255;offset++;}if (offset < size) {payload_type += data[offset++];}// Read payload sizewhile (offset < size && data[offset] == 0xFF) {payload_size += 255;offset++;}if (offset < size) {payload_size += data[offset++];}// Payload dataif (offset + payload_size > size) {std::cerr << "Invalid SEI payload size" << std::endl;return;}uint8_t* payload_data = (uint8_t*)data + offset;offset += payload_size;// Here you can parse the specific SEI payload data based on payload_typestd::cout << "SEI message: type=" << (int)payload_type << ", size=" << (int)payload_size << std::endl;if (payload_type == 5){pParsedData[0] = payload_data;nSize = payload_size;break;}}
}

这篇关于使用ffmpeg的c++库读取视频流和其中的SEI数据的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1130547

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<