音视频入门基础: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 设备的恶意软件类型,并为您…...
LabelMe高级应用:如何利用AI辅助标注提升效率300%
LabelMe高级应用:如何利用AI辅助标注提升效率300% LabelMe是一款强大的图像标注工具,支持多边形、矩形、圆形、线条、点和图像级标记等多种标注方式。对于AI训练数据准备工作而言,高效的标注工具能显著提升工作流效率。本文将详细介绍如何利…...
COMSOL—超声相控阵聚焦仿真 模型介绍:激励函数是由高斯波和正弦波组成的脉冲函数
COMSOL—超声相控阵聚焦仿真 模型介绍:激励函数是由高斯波和正弦波组成的脉冲函数超声相控阵这玩意儿在工业检测和医学影像里玩得可溜了,今天咱们整点硬核的——用COMSOL搞个带高斯调制的超声聚焦仿真。先看这个模型的灵魂所在:激励信号设计。…...
强强联合!望石智慧携手华为、华鲲振宇发布AI药物研发联合解决方案,共筑中国智慧医药创新生态
近日,以“因聚而升 融智有为”为主题的华为中国合作伙伴大会2026在深圳圆满落幕。望石智慧作为其国内AI驱动医药创新领域的核心技术伙伴受邀参会,并在智能制造医药行业论坛发表演讲。会议期间,望石智慧、华为、华鲲振宇三方达成战略级生态合作…...
终极实战指南:在Docker容器中运行Windows系统的完整解决方案
终极实战指南:在Docker容器中运行Windows系统的完整解决方案 【免费下载链接】windows Windows inside a Docker container. 项目地址: https://gitcode.com/GitHub_Trending/wi/windows 还在为Windows虚拟机占用大量系统资源而烦恼吗?想体验在容…...
Cocos解耦移动和发射模块
目标:玩家受到摇杆A控制移动和方向,发射受到摇杆B负责方向和发射 //玩家模块 ccclass(Player) export class Player extends Component {//玩家速度Speed:number 500;//玩家方向property(Vec3)PlayerDir:Vec3;//虚拟摇杆property(Node)Joystick:Node n…...
3分钟完成Axure RP中文界面汉化:终极完整指南
3分钟完成Axure RP中文界面汉化:终极完整指南 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包,不定期更新。支持 Axure 9、Axure 10。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axu…...
告别丑陋代码块!用PyQt5+pygments实现Word代码高亮粘贴(附完整源码)
告别丑陋代码块!用PyQt5pygments实现Word代码高亮粘贴(附完整源码) 在技术文档编写过程中,代码展示是不可或缺的部分。然而,直接将IDE中的代码复制到Word文档时,往往会丢失原有的高亮和格式,变成…...
CAD工程师必看:如何用De Boor算法优化B样条曲线设计(附NURBS对比)
CAD工程师必看:如何用De Boor算法优化B样条曲线设计(附NURBS对比) 在工业设计领域,曲线建模的精度与效率直接决定了产品从概念到成品的转化质量。作为CAD工程师,我们常常需要在设计自由度和计算效率之间寻找平衡点——…...
2025新算法TOC优化VMD实战:六种熵值评估信号分解,一键Matlab出图
1. 为什么需要优化VMD参数? 第一次接触VMD(Variational Mode Decomposition)时,我和很多初学者一样被它的参数调优问题困扰。记得当时处理一组轴承振动信号,手动试了十几组K值和α值,结果要么模态分解不彻底…...
pdf2htmlEX高级调试技术:汇编级调试与反汇编
pdf2htmlEX高级调试技术:汇编级调试与反汇编 【免费下载链接】pdf2htmlEX Convert PDF to HTML without losing text or format. 项目地址: https://gitcode.com/gh_mirrors/pd/pdf2htmlEX pdf2htmlEX是一款能够将PDF文件转换为HTML格式同时保持文本和格式完…...
