音视频入门基础:FLV专题(14)——FFmpeg源码中,解码Script Tag的实现
一、引言
在《音视频入门基础:FLV专题(9)——Script Tag简介》中对Script Tag进行了简介,本文讲述FFmpeg源码中是怎样解码FLV文件的Script Tag,拿到里面的信息。
二、flv_read_packet函数
从《音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现》可以知道,FFmpeg源码中使用flv_read_packet函数来读取每个Tag的信息,该函数的前半部分实现了解码Tag header,获取其TagType属性的功能。然后根据TagType属性的值,判断该Tag为音频Tag、视频Tag还是脚本Tag。根据Tag的类型分别执行不同的解码操作:
if (type == FLV_TAG_TYPE_AUDIO) {//...} else if (type == FLV_TAG_TYPE_VIDEO) {//...}else if (type == FLV_TAG_TYPE_META) {//...}else{//...}//...
从《音视频入门基础:FLV专题(9)——Script Tag简介》可以知道:
Script Tag = Tag header + SCRIPTDATA
未加密的情况下,SCRIPTDATA = ScriptTagBody
而FFmpeg源码(截止源码版本7.0.1)会统一把FLV文件当做未加密来处理。
所以FFmpeg源码中:Script Tag = Tag header + ScriptTagBody
如果在flv_read_packet函数的前半部分判断出该Tag为Script Tag(脚本Tag),flv_read_packet函数中会执行如下逻辑解码Script Tag的ScriptTagBody:
else if (type == FLV_TAG_TYPE_META) {stream_type=FLV_STREAM_TYPE_SUBTITLE;if (size > 13 + 1 + 4) { // Header-type metadata stuffint type;meta_pos = avio_tell(s->pb);type = flv_read_metabody(s, next);if (type == 0 && dts == 0 || type < 0) {if (type < 0 && flv->validate_count &&flv->validate_index[0].pos > next &&flv->validate_index[0].pos - 4 < next) {av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n");next = flv->validate_index[0].pos - 4;}goto skip;} else if (type == TYPE_ONTEXTDATA) {avpriv_request_sample(s, "OnTextData packet");return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_ONCAPTION) {return flv_data_packet(s, pkt, dts, next);} else if (type == TYPE_UNKNOWN) {stream_type = FLV_STREAM_TYPE_DATA;}avio_seek(s->pb, meta_pos, SEEK_SET);}}else {av_log(s, AV_LOG_DEBUG,"Skipping flv packet: type %d, size %d, flags %d.\n",type, size, flags);
skip:if (avio_seek(s->pb, next, SEEK_SET) != next) {// This can happen if flv_read_metabody above read past// next, on a non-seekable input, and the preceding data has// been flushed out from the IO buffer.av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n");return AVERROR_INVALIDDATA;}ret = FFERROR_REDO;goto leave;
//...
leave:
//...return ret;}
下面我们分析上述代码块中解码ScriptTagBody的原理。
三、flv_read_packet函数中解码ScriptTagBody的原理
从 《音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现》中可以知道,上述代码块中,局部变量size存贮该Tag的Tag data的长度,当该Tag为Script Tag时,它的Tag data就是ScriptTagBody,所以此时局部变量size存贮ScriptTagBody的长度:
else if (type == FLV_TAG_TYPE_META) {stream_type=FLV_STREAM_TYPE_SUBTITLE;if (size > 13 + 1 + 4) { // Header-type metadata stuff//...}
从《音视频入门基础:FLV专题(9)——Script Tag简介》可以知道:
ScriptTagBody = Name + Value
ScriptTagBody的Name = 1字节的值为2的Type + 2字节的StringLength + 可变长的StringData
ScriptTagBody的Value = 1字节的值为8的Type + 4字节的ECMAArrayLength + Variables数组 + 3字节的终止符(值固定为0,0,9)
FLV文件中必然有一个名称为“onMetadata”的Script Tag。当Script Tag为“onMetadata”时,ScriptTagBody的Name的StringData必为10个字节(“onMetadata”总共10个字节)。
所以当Script Tag为“onMetadata”时,ScriptTagBody的Name = 1字节的值为2的Type + 2字节的StringLength + 10字节的StringData(“onMetadata”),这时ScriptTagBody的Name固定为13字节。
ScriptTagBody的Value = 1字节的值为8的Type + 4字节的ECMAArrayLength + Variables数组 + 3字节的终止符(值固定为0,0,9),ScriptTagBody的Value必然不小于(1 + 4 + 3)个字节。
所以整个ScriptTagBody必然不小于(13 + 1 + 4 + 3)个字节。
所以flv_read_packet函数解码ScriptTagBody时,首先判断ScriptTagBody长度是否大于(13 + 1 + 4)个字节,如果不大于,不执行解码ScriptTagBody的操作。[个人认为FFmpeg源码中if (size > 13 + 1 + 4)这部分判断不够严谨,没有考虑到3字节终止符的情况,应该改为if (size > 13 + 1 + 4 + 3)]:
if (size > 13 + 1 + 4) { // Header-type metadata stuff//...}
通过avio_tell函数得到该Script Tag的ScriptTagBody的第一个字节相对于FLV文件的文件首的偏移字节数,赋值给局部变量meta_pos。关于avio_tell函数的用法可以参考:《FFmpeg源码:avio_tell函数分析》:
meta_pos = avio_tell(s->pb);
执行语句:
int type;
//...
type = flv_read_metabody(s, next);
flv_read_metabody函数定义如下。可以看到flv_read_metabody函数中,首先通过语句:type = avio_r8(ioc)得到该Tag的ScriptTagBody的Name中的Type,然后通过语句:amf_get_string(ioc, buffer, sizeof(buffer))得到ScriptTagBody的Name中的StringData。最后通过语句:amf_parse_object(s, astream, vstream, buffer, next_pos, 0)解析ScriptTagBody的Value。从《音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现》中可以知道,当Script Tag的名称为“onMetadata”时,通过amf_parse_object函数可以解析出部分元数据属性(duration、videodatarate、audiodatarate等)。当amf_parse_object函数解析成功时,flv_read_metabody函数返回0:
static int flv_read_metabody(AVFormatContext *s, int64_t next_pos)
{FLVContext *flv = s->priv_data;AMFDataType type;
//...// first object needs to be "onMetaData" stringtype = avio_r8(ioc);if (type != AMF_DATA_TYPE_STRING ||amf_get_string(ioc, buffer, sizeof(buffer)) < 0)return TYPE_UNKNOWN;
//...// parse the second object (we want a mixed array)if (amf_parse_object(s, astream, vstream, buffer, next_pos, 0) < 0)return -1;return 0;
}
所以flv_read_packet函数中,如果Script Tag的名称为“onMetadata”,并且解析ScriptTagBody的Value成功,type的值为0:
type = flv_read_metabody(s, next);
该Tag为Script Tag时Tag header中的Timestamp必为0,所以dts为0。所以满足条件“type == 0 && dts == 0”,执行下面大括号中的内容。执行语句:goto skip:
if (type == 0 && dts == 0 || type < 0) {if (type < 0 && flv->validate_count &&flv->validate_index[0].pos > next &&flv->validate_index[0].pos - 4 < next) {av_log(s, AV_LOG_WARNING, "Adjusting next position due to index mismatch\n");next = flv->validate_index[0].pos - 4;}goto skip;}
从《音视频入门基础:FLV专题(8)——FFmpeg源码中,解码Tag header的实现》中可以知道,局部变量next为该Tag对应的PreviousTagSize相对于文件首的偏移(单位为字节)。通过avio_seek函数把AVIOContext的文件位置指针移动到该Tag对应的PreviousTagSize处,关于avio_seek函数的用法可以参考:《FFmpeg源码:avio_seek函数分析》。如果移动失败或者实际移动到的位置不是PreviousTagSize,表示出错了,flv_read_packet函数返回AVERROR_INVALIDDATA。如果没有出错,flv_read_packet函数返回FFERROR_REDO:
skip:if (avio_seek(s->pb, next, SEEK_SET) != next) {// This can happen if flv_read_metabody above read past// next, on a non-seekable input, and the preceding data has// been flushed out from the IO buffer.av_log(s, AV_LOG_ERROR, "Unable to seek to the next packet\n");return AVERROR_INVALIDDATA;}ret = FFERROR_REDO;goto leave;
//...
leave:
//...return ret;
宏FFERROR_REDO定义如下,表示数据被消耗但被丢弃(被忽略的流或垃圾数据)。当使用flv_read_packet函数读取名称为“onMetadata”的Script Tag时,因为该Tag不是音频Tag或视频Tag,不包含实际的音视频数据,所以读取成功时flv_read_packet函数会返回FFERROR_REDO:
/*** Returned by demuxers to indicate that data was consumed but discarded* (ignored streams or junk data). The framework will re-call the demuxer.*/
#define FFERROR_REDO FFERRTAG('R','E','D','O')
四、总结
1.flv_read_packet函数的作用是读取每个Tag的信息,即解码某个Tag。该函数首先会解码Tag header,获取其TagType。然后根据TagType的值,判断该Tag为音频Tag、视频Tag还是脚本Tag。根据Tag的类型分别执行不同的解码操作。
2.该Tag为Script Tag(脚本Tag)的情况下,flv_read_packet函数返回一个负数(FFERROR_REDO)是正常的,表示数据被消耗但被丢弃。
相关文章:
音视频入门基础:FLV专题(14)——FFmpeg源码中,解码Script Tag的实现
一、引言 在《音视频入门基础:FLV专题(9)——Script Tag简介》中对Script Tag进行了简介,本文讲述FFmpeg源码中是怎样解码FLV文件的Script Tag,拿到里面的信息。 二、flv_read_packet函数 从《音视频入门基础&#x…...
小猿口算APP脚本(协议版)
小猿口算是一款专注于数学学习的教育应用,主要面向小学阶段的学生。它提供多种数学练习和测试,包括口算、速算、应用题等。通过智能化的题目生成和实时批改功能,帮助学生提高数学计算能力。此外,它还提供详细的学习报告和分析,帮助家长和教师了解学生的学习进度和薄弱环节…...
【长文梳理webserver核心】核心类篇
前言 有三个核心组件支撑一个reactor实现 [持续] 的 [监听] 一组fd,并根据每个fd上发生的事件 [调用] 相应的处理函数。这三个组件就是 EventLoop 、Channel 以及 Poller 三个类,其中 EventLoop 可以看作是对业务线程的封装,而 Channel 可以看…...
[实用工具]Docker安装nextcloud实现私有云服务和onlyoffice
Nextcloud是一款开源的云存储和协作平台,允许用户在自己的服务器上存储和访问文件,同时提供强大的协作工具。它可以替代商业云存储服务,让用户拥有完全控制和自主管理自己的数据。 Nextcloud支持文件上传和下载,可以通过Web界面、…...
基于STM32设计的生猪健康检测管理系统(NBIOT+OneNet)(240)
文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成1.2 设计思路1.3 项目开发背景【1】选题的意义【2】可行性分析【3】参考文献【4】项目背景【5】摘要1.4 开发工具的选择【1】设备端开发【2】上位机开发1.5 系统功能总结1.6 系统框架图…...
springboot kafka多数据源,通过配置动态加载发送者和消费者
前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需…...
【华为】基于华为交换机的VLAN配置与不同VLAN间通信实现
划分VLAN(虚拟局域网)主要作用: 一、提高网络安全性 广播域隔离访问控制增强 二、优化网络性能 减少网络拥塞提高网络可管理性 sysytem-view #进入系统视图配置参数 vlan batch 10 20 #批量创建vlan LSW3: int g0/0/1 port…...
力扣题11~20
题11(中等): 思路: 这种题目第一眼就是双循环,但是肯定不行滴,o(n^2)这种肯定超时,很难接受。 所以要另辟蹊径,我们先用俩指针(标志位)在最左端和最右端&am…...
更美观的HTTP性能监测工具:httpstat
reorx/httpstat是一个旨在提供更美观和详细HTTP请求统计信息的cURL命令行工具,它能够帮助开发者和运维人员深入理解HTTP请求的性能和状态。 1. 基本概述 项目地址:https://github.com/reorx/httpstat语言:该工具主要是以Python编写ÿ…...
在2024 VDC,听一曲“蓝心智能”的江河协奏
作为科技从业者,我们每年参加的终端产品发布会和开发者大会,少则几十场。说每一场都别有新意,那自然是不可能的,但每次去vivo的活动现场,总能给我耳目一新的感觉。 雨果说过,音乐可以表达难以用语言描述&am…...
Python编写的数字光刻仿真程序,使用了Hopkins光刻模型和粒子群优化(PSO)算法来优化掩模设计
Python编写的数字光刻仿真程序,使用了Hopkins光刻模型和粒子群优化(PSO)算法来优化掩模设计,以减少光刻过程中的图形偏差。 4. 定义了几个函数来模拟光波通过光刻系统的变化: - `transfer_function`:计算光波的相位变化。 - `light_source_function`:描述光源在各…...
【AD那些事 11】绘制PCB板时“隔离” 的那些事(笔记摘抄)
在设计新板子时发现需要考虑隔离!!!!!!!!!!!于是我在网上找了很多资料,摘抄了一些,整理了一下,作为笔记&#…...
sublime配置(竞赛向)
我也想要有jiangly一样的sublime 先决条件 首先,到官网上下载最新的sublime4,然后在mingw官网上下载最新的mingw64 mingw64官网:左边菜单栏点击dowloads,然后选择MinGW-W64-builds(可能会有点慢)——然后有时候会变成选LLVM-minGW,接着选择…...
双向数据库迁移工具:轻松实现 MySQL 与 SQLite 数据互导
项目概述与作用 该项目的核心是实现 MySQL 和 SQLite 两种数据库之间的数据迁移工具。它能够轻松地将 MySQL 数据库中的数据导出为 SQLite 数据库文件,反过来也可以将 SQLite 数据库中的数据上传到 MySQL 数据库中。这个双向迁移工具非常适用于: 数据库备…...
oracle查询表空间信息
方式一,通过SQLPLUS查看,适用于无PLSQL等工具 sqlplus / as sysdba set line 200 set lines 200 col tablespace_name for a20 col SUM_SPACE(M) for a15 col USED_SPACE(M) for a15 col USED_RATE(%) for a15 col FREE_SPACE(M) for a15 SELEC…...
使用Python编写你的第一个算法交易程序
背景 Background 最近想学习一下量化金融,总算在盈透投资者教育(IBKRCampus)板块找到一篇比较好的算法交易入门教程。我在记录实践过程后,翻译成中文写成此csdn博客,分享给大家。 如果你的英语好可以直接看原文…...
点进HTML初步了解
写在前边 ##关于插件 ①简体中文 ②open-in-browser:自动在浏览器生成html页面; ③Auto Rename Tag:自动匹配标签; ④Live server:实现页面的实时刷新; ##关于快捷键: Ctrl / 用来注释…...
幸运的沈抖,进击的百度智能云
文|白 鸽 编|王一粟 AI对百度智能云的意义,可能远大于任何一家云计算厂商。 2022年5月,分管百度移动生态事业群组(MEG)的集团执行副总裁沈抖,转而担任百度智能云事业群组(ACG&…...
android广播实现PIN码设置
摘要:本文通过广播的方式调用系统设置PIN码的流程实现类似锁机的功能,可供开发人员在联网状态下后台推送消息进行锁机/解锁。有需要的同学可以参考PIN码的流程改为密码等其他形式。 1 定义一个广播接收器 广播action:android.intent.action…...
Mac 需要杀毒软件?
大部分 mac用户普遍认为 Apple mac 不受病毒和恶意软件的影响。这导致许多 Mac 用户误以为无需为 Mac 安装防病毒软件,但事实并非如此。 在这篇文章中,将深入探讨 Mac 安全性的细节,探索针对 Apple 设备的恶意软件类型,并为您…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
HTTPS证书一年多少钱?
HTTPS证书作为保障网站数据传输安全的重要工具,成为众多网站运营者的必备选择。然而,面对市场上种类繁多的HTTPS证书,其一年费用究竟是多少,又受哪些因素影响呢? 首先,HTTPS证书通常在PinTrust这样的专业平…...
使用VMware克隆功能快速搭建集群
自己搭建的虚拟机,后续不管是学习java还是大数据,都需要集群,java需要分布式的微服务,大数据Hadoop的计算集群,如果从头开始搭建虚拟机会比较费时费力,这里分享一下如何使用克隆功能快速搭建一个集群 先把…...
