当前位置: 首页 > news >正文

音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现

一、SCRIPTDATAVALUE类型

从《音视频入门基础:FLV专题(9)——Script Tag简介》中可以知道,根据《video_file_format_spec_v10_1.pdf》第80到81页,SCRIPTDATAVALUE类型由一个8位(1字节)的Type和一个ScriptDataValue组成。其中Type属性用来指定ScriptDataValue的类型,根据Type值的不同,ScriptDataValue的类型也不同:

二、ScriptTagBody

根据《video_file_format_spec_v10_1.pdf》第80页,ScriptTagBody包含以Action Message Format(AMF)编码的SCRIPTDATA。AMF是一种用于数据交换的开放标准,旨在简化数据传输和解析过程。ScriptTagBody由Name和Value组成,这两个属性都是SCRIPTDATAVALUE类型的:

ScriptTagBody = Name + Value

三、SCRIPTDATAOBJECTPROPERTY类型

ScriptTagBody的Value属性指定了AMF参数或对象的属性,为SCRIPTDATAVALUE类型。当Value对应的Type的值为8时,Value对应的ScriptDataValue属性为SCRIPTDATAECMAARRAY类型。

SCRIPTDATAECMAARRAY类型的Variables属性是一个数组,该数组的每个元素都为SCRIPTDATAOBJECTPROPERTY类型。数组中的每个元素由变量名称(PropertyName)和变量的值(PropertyData)组成。根据《video_file_format_spec_v10_1.pdf》第82页,PropertyName为SCRIPTDATASTRING类型,而PropertyData为SCRIPTDATAVALUE类型:

SCRIPTDATAOBJECTPROPERTY类型 = PropertyName + PropertyData

四、ScriptTagBody和SCRIPTDATAOBJECTPROPERTY类型的相似之处

从上面可以看出来,ScriptTagBody和SCRIPTDATAOBJECTPROPERTY类型,这两者的结构是非常相似的,它们都是由一个Name和一个Value/Data组成,特别是它们的第二个属性:Value和Data都是SCRIPTDATAVALUE类型。所以FFmpeg封装了一个通用的解析函数——amf_parse_object函数来实现解析ScriptTagBody的Value属性或SCRIPTDATAOBJECTPROPERTY类型的PropertyData属性。也就是说FFmpeg源码中,amf_parse_object函数是解析SCRIPTDATAVALUE类型的通用方法。不管是什么Type值的SCRIPTDATAVALUE类型都可以通过amf_parse_object函数把其Type和ScriptDataValue解析出来。

五、amf_parse_object函数的定义

amf_parse_object函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的源文件libavformat/flvdec.c中:

static int amf_parse_object(AVFormatContext *s, AVStream *astream,AVStream *vstream, const char *key,int64_t max_pos, int depth)
{AVCodecParameters *apar, *vpar;FLVContext *flv = s->priv_data;AVIOContext *ioc;AMFDataType amf_type;FLVMetaVideoColor *meta_video_color = flv->metaVideoColor;char str_val[1024];double num_val;amf_date date;if (depth > MAX_DEPTH)return AVERROR_PATCHWELCOME;num_val  = 0;ioc      = s->pb;if (avio_feof(ioc))return AVERROR_EOF;amf_type = avio_r8(ioc);switch (amf_type) {case AMF_DATA_TYPE_NUMBER:num_val = av_int2double(avio_rb64(ioc));break;case AMF_DATA_TYPE_BOOL:num_val = avio_r8(ioc);break;case AMF_DATA_TYPE_STRING:if (amf_get_string(ioc, str_val, sizeof(str_val)) < 0) {av_log(s, AV_LOG_ERROR, "AMF_DATA_TYPE_STRING parsing failed\n");return -1;}break;case AMF_DATA_TYPE_OBJECT:if (key &&(ioc->seekable & AVIO_SEEKABLE_NORMAL) &&!strcmp(KEYFRAMES_TAG, key) && depth == 1)if (parse_keyframes_index(s, ioc, max_pos) < 0)av_log(s, AV_LOG_ERROR, "Keyframe index parsing failed\n");elseadd_keyframes_index(s);while (avio_tell(ioc) < max_pos - 2 &&amf_get_string(ioc, str_val, sizeof(str_val)) > 0)if (amf_parse_object(s, astream, vstream, str_val, max_pos,depth + 1) < 0)return -1;     // if we couldn't skip, bomb out.if (avio_r8(ioc) != AMF_END_OF_OBJECT) {av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_OBJECT\n");return -1;}break;case AMF_DATA_TYPE_NULL:case AMF_DATA_TYPE_UNDEFINED:case AMF_DATA_TYPE_UNSUPPORTED:break;     // these take up no additional spacecase AMF_DATA_TYPE_MIXEDARRAY:{unsigned v;avio_skip(ioc, 4);     // skip 32-bit max array indexwhile (avio_tell(ioc) < max_pos - 2 &&amf_get_string(ioc, str_val, sizeof(str_val)) > 0)// this is the only case in which we would want a nested// parse to not skip over the objectif (amf_parse_object(s, astream, vstream, str_val, max_pos,depth + 1) < 0)return -1;v = avio_r8(ioc);if (v != AMF_END_OF_OBJECT) {av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_MIXEDARRAY, found %d\n", v);return -1;}break;}case AMF_DATA_TYPE_ARRAY:{unsigned int arraylen, i;arraylen = avio_rb32(ioc);for (i = 0; i < arraylen && avio_tell(ioc) < max_pos - 1; i++)if (amf_parse_object(s, NULL, NULL, NULL, max_pos,depth + 1) < 0)return -1;      // if we couldn't skip, bomb out.}break;case AMF_DATA_TYPE_DATE:// timestamp (double) and UTC offset (int16)date.milliseconds = av_int2double(avio_rb64(ioc));date.timezone = avio_rb16(ioc);break;default:                    // unsupported type, we couldn't skipav_log(s, AV_LOG_ERROR, "unsupported amf type %d\n", amf_type);return -1;}if (key) {apar = astream ? astream->codecpar : NULL;vpar = vstream ? vstream->codecpar : NULL;// stream info doesn't live any deeper than the first objectif (depth == 1) {if (amf_type == AMF_DATA_TYPE_NUMBER ||amf_type == AMF_DATA_TYPE_BOOL) {if (!strcmp(key, "duration"))s->duration = num_val * AV_TIME_BASE;else if (!strcmp(key, "videodatarate") &&0 <= (int)(num_val * 1024.0))flv->video_bit_rate = num_val * 1024.0;else if (!strcmp(key, "audiodatarate") &&0 <= (int)(num_val * 1024.0))flv->audio_bit_rate = num_val * 1024.0;else if (!strcmp(key, "framerate")) {flv->framerate = av_d2q(num_val, 1000);if (vstream)vstream->avg_frame_rate = flv->framerate;} else if (flv->trust_metadata) {if (!strcmp(key, "videocodecid") && vpar) {int ret = flv_set_video_codec(s, vstream, num_val, 0);if (ret < 0)return ret;} else if (!strcmp(key, "audiocodecid") && apar) {int id = ((int)num_val) << FLV_AUDIO_CODECID_OFFSET;flv_set_audio_codec(s, astream, apar, id);} else if (!strcmp(key, "audiosamplerate") && apar) {apar->sample_rate = num_val;} else if (!strcmp(key, "audiosamplesize") && apar) {apar->bits_per_coded_sample = num_val;} else if (!strcmp(key, "stereo") && apar) {av_channel_layout_default(&apar->ch_layout, num_val + 1);} else if (!strcmp(key, "width") && vpar) {vpar->width = num_val;} else if (!strcmp(key, "height") && vpar) {vpar->height = num_val;} else if (!strcmp(key, "datastream")) {AVStream *st = create_stream(s, AVMEDIA_TYPE_SUBTITLE);if (!st)return AVERROR(ENOMEM);st->codecpar->codec_id = AV_CODEC_ID_TEXT;}}}if (amf_type == AMF_DATA_TYPE_STRING) {if (!strcmp(key, "encoder")) {int version = -1;if (1 == sscanf(str_val, "Open Broadcaster Software v0.%d", &version)) {if (version > 0 && version <= 655)flv->broken_sizes = 1;}} else if (!strcmp(key, "metadatacreator")) {if (   !strcmp (str_val, "MEGA")|| !strncmp(str_val, "FlixEngine", 10))flv->broken_sizes = 1;}}}if (meta_video_color) {if (amf_type == AMF_DATA_TYPE_NUMBER ||amf_type == AMF_DATA_TYPE_BOOL) {if (!strcmp(key, "colorPrimaries")) {meta_video_color->primaries = num_val;} else if (!strcmp(key, "transferCharacteristics")) {meta_video_color->transfer_characteristics = num_val;} else if (!strcmp(key, "matrixCoefficients")) {meta_video_color->matrix_coefficients = num_val;} else if (!strcmp(key, "maxFall")) {meta_video_color->max_fall = num_val;} else if (!strcmp(key, "maxCLL")) {meta_video_color->max_cll = num_val;} else if (!strcmp(key, "redX")) {meta_video_color->mastering_meta.r_x = num_val;} else if (!strcmp(key, "redY")) {meta_video_color->mastering_meta.r_y = num_val;} else if (!strcmp(key, "greenX")) {meta_video_color->mastering_meta.g_x = num_val;} else if (!strcmp(key, "greenY")) {meta_video_color->mastering_meta.g_y = num_val;} else if (!strcmp(key, "blueX")) {meta_video_color->mastering_meta.b_x = num_val;} else if (!strcmp(key, "blueY")) {meta_video_color->mastering_meta.b_y = num_val;} else if (!strcmp(key, "whitePointX")) {meta_video_color->mastering_meta.white_x = num_val;} else if (!strcmp(key, "whitePointY")) {meta_video_color->mastering_meta.white_y = num_val;} else if (!strcmp(key, "maxLuminance")) {meta_video_color->mastering_meta.max_luminance = num_val;} else if (!strcmp(key, "minLuminance")) {meta_video_color->mastering_meta.min_luminance = num_val;}}}if (amf_type == AMF_DATA_TYPE_OBJECT && s->nb_streams == 1 &&((!apar && !strcmp(key, "audiocodecid")) ||(!vpar && !strcmp(key, "videocodecid"))))s->ctx_flags &= ~AVFMTCTX_NOHEADER; //If there is either audio/video missing, codecid will be an empty objectif ((!strcmp(key, "duration")        ||!strcmp(key, "filesize")        ||!strcmp(key, "width")           ||!strcmp(key, "height")          ||!strcmp(key, "videodatarate")   ||!strcmp(key, "framerate")       ||!strcmp(key, "videocodecid")    ||!strcmp(key, "audiodatarate")   ||!strcmp(key, "audiosamplerate") ||!strcmp(key, "audiosamplesize") ||!strcmp(key, "stereo")          ||!strcmp(key, "audiocodecid")    ||!strcmp(key, "datastream")) && !flv->dump_full_metadata)return 0;s->event_flags |= AVFMT_EVENT_FLAG_METADATA_UPDATED;if (amf_type == AMF_DATA_TYPE_BOOL) {av_strlcpy(str_val, num_val > 0 ? "true" : "false",sizeof(str_val));av_dict_set(&s->metadata, key, str_val, 0);} else if (amf_type == AMF_DATA_TYPE_NUMBER) {snprintf(str_val, sizeof(str_val), "%.f", num_val);av_dict_set(&s->metadata, key, str_val, 0);} else if (amf_type == AMF_DATA_TYPE_STRING) {av_dict_set(&s->metadata, key, str_val, 0);} else if (   amf_type == AMF_DATA_TYPE_DATE&& isfinite(date.milliseconds)&& date.milliseconds > INT64_MIN/1000&& date.milliseconds < INT64_MAX/1000) {// timezone is ignored, since there is no easy way to offset the UTC// timestamp into the specified timezoneavpriv_dict_set_timestamp(&s->metadata, key, 1000 * (int64_t)date.milliseconds);}}return 0;
}

该函数作用就是解析SCRIPTDATAVALUE类型,把其Type属性和ScriptDataValue属性解析出来。

形参s:既是输入型参数也是输出型参数,指向一个AVFormatContext类型变量。执行amf_parse_object函数前,s->pb->buf_ptr必须指向待解析的SCRIPTDATAVALUE类型数据的开头,也就是其Type属性。如果要解析的是“onMetadata”中的元数据属性,执行amf_parse_object函数后,s->duration和(FLVContext *)(s->priv_data)会得到解析出来的部分属性(duration、videodatarate、audiodatarate等)。

形参astream:输出型参数,指向一个AVStream音频流对象,值一般为NULL。

形参vstream:输出型参数,指向一个AVStream视频流对象,值一般为NULL。

形参key:输入型参数。指向从SCRIPTDATASTRING类型的StringData属性中解析出来的字符串数据。

1.如果要解析的是ScriptTagBody的Value,key指向“从该ScriptTagBody的Name属性的StringData属性中解析出来的字符串数据”,比如:“onMetadata”。

2.如果要解析的是SCRIPTDATAOBJECTPROPERTY类型的PropertyData属性,key指向“从该SCRIPTDATAOBJECTPROPERTY类型的PropertyName属性的StringData属性中解析出来的字符串数据”,比如“duration”。

形参max_pos:输入型参数。所在Tag对应的PreviousTagSize相对于文件首的偏移(单位为字节)。

  

形参depth:输入型参数,表示该SCRIPTDATAVALUE类型数据的递归深度。该值从0开始,每递归调用自身的次数加1,depth的值加1。

返回值:返回一个负数表示解析失败;返回0表示解析成功。

六、amf_parse_object函数的内部实现原理

在前文中我们讲到,当SCRIPTDATAVALUE类型的Type值为8时,其ScriptDataValue属性为SCRIPTDATAECMAARRAY类型。SCRIPTDATAECMAARRAY类型的Variables属性是一个数组,该数组的每个元素都为SCRIPTDATAOBJECTPROPERTY类型。而SCRIPTDATAOBJECTPROPERTY类型的PropertyData属性又是SCRIPTDATAVALUE类型。

所以解析SCRIPTDATAVALUE类型需要用到递归,即amf_parse_object函数内部又会自我调用。形参depth表示该SCRIPTDATAVALUE类型数据的递归深度。该值从0开始,每递归调用自身的次数加1,depth的值加1。

宏定义MAX_DEPTH的值为16:

#define MAX_DEPTH 16      ///< arbitrary limit to prevent unbounded recursion

为了防止无限递归导致程序崩溃,amf_parse_object函数中首先判断递归深度是否大于16。如果大于16,返回AVERROR_PATCHWELCOME表示出错:

    if (depth > MAX_DEPTH)return AVERROR_PATCHWELCOME;

判断是否是因为读取到文件末尾而结束。如果是,返回AVERROR_EOF表示读取文件结束。关于avio_feof函数的用法可以参考:《FFmpeg源码:avio_feof函数分析》:

    ioc      = s->pb;if (avio_feof(ioc))return AVERROR_EOF;

读取SCRIPTDATAVALUE类型的Type属性。关于avio_r8函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》。变量amf_type为枚举变量:

    AMFDataType amf_type;//...    amf_type = avio_r8(ioc);

枚举名称AMFDataType声明如下,对应SCRIPTDATAVALUE类型的Type属性:

typedef enum {AMF_DATA_TYPE_NUMBER      = 0x00,AMF_DATA_TYPE_BOOL        = 0x01,AMF_DATA_TYPE_STRING      = 0x02,AMF_DATA_TYPE_OBJECT      = 0x03,AMF_DATA_TYPE_NULL        = 0x05,AMF_DATA_TYPE_UNDEFINED   = 0x06,AMF_DATA_TYPE_REFERENCE   = 0x07,AMF_DATA_TYPE_MIXEDARRAY  = 0x08,AMF_DATA_TYPE_OBJECT_END  = 0x09,AMF_DATA_TYPE_ARRAY       = 0x0a,AMF_DATA_TYPE_DATE        = 0x0b,AMF_DATA_TYPE_LONG_STRING = 0x0c,AMF_DATA_TYPE_UNSUPPORTED = 0x0d,
} AMFDataType;

根据Type值的不同,执行不同的解析ScriptDataValue的方法:

    switch (amf_type) {case AMF_DATA_TYPE_NUMBER://...break;case AMF_DATA_TYPE_BOOL://...break;case AMF_DATA_TYPE_STRING://...break;case AMF_DATA_TYPE_OBJECT://...break;case AMF_DATA_TYPE_NULL:case AMF_DATA_TYPE_UNDEFINED:case AMF_DATA_TYPE_UNSUPPORTED:break;     // these take up no additional spacecase AMF_DATA_TYPE_MIXEDARRAY://...break;}case AMF_DATA_TYPE_ARRAY://...break;case AMF_DATA_TYPE_DATE://...default:                    // unsupported type, we couldn't skipav_log(s, AV_LOG_ERROR, "unsupported amf type %d\n", amf_type);return -1;}

下面分情况讨论:

(一)Type的值为0

当Type的值为0(即变量amf_type为AMF_DATA_TYPE_NUMBER)时,ScriptDataValue为DOUBLE类型。此时首先通过avio_rb64(ioc)读取8个字节, 然后通过av_int2double函数将这8个字节数据转换为double类型赋值给变量num_val。关于av_int2double函数的的用法可以参考:《音视频入门基础:FLV专题(12)——FFmpeg源码中,解析DOUBLE类型的ScriptDataValue的实现》:

    double num_val;//...case AMF_DATA_TYPE_NUMBER:num_val = av_int2double(avio_rb64(ioc));break;

(二)Type的值为2

当Type的值为2(即变量amf_type为AMF_DATA_TYPE_STRING)时,ScriptDataValue为SCRIPTDATASTRING类型。此时通过amf_get_string函数解析SCRIPTDATASTRING类型的ScriptDataValue,让数组str_val得到从SCRIPTDATASTRING类型的ScriptDataValue中解析出来的实际的字符串数据。关于amf_get_string函数的用法可以参考:《音视频入门基础:FLV专题(11)——FFmpeg源码中,解析SCRIPTDATASTRING类型的ScriptDataValue的实现》:

    char str_val[1024];//...case AMF_DATA_TYPE_STRING:if (amf_get_string(ioc, str_val, sizeof(str_val)) < 0) {av_log(s, AV_LOG_ERROR, "AMF_DATA_TYPE_STRING parsing failed\n");return -1;}break;

(三)Type的值为8

当Type的值为8(即变量amf_type为AMF_DATA_TYPE_MIXEDARRAY)时,ScriptDataValue为SCRIPTDATAECMAARRAY类型,执行下面的逻辑解析SCRIPTDATAECMAARRAY类型:

    case AMF_DATA_TYPE_MIXEDARRAY:{unsigned v;avio_skip(ioc, 4);     // skip 32-bit max array indexwhile (avio_tell(ioc) < max_pos - 2 &&amf_get_string(ioc, str_val, sizeof(str_val)) > 0)// this is the only case in which we would want a nested// parse to not skip over the objectif (amf_parse_object(s, astream, vstream, str_val, max_pos,depth + 1) < 0)return -1;v = avio_r8(ioc);if (v != AMF_END_OF_OBJECT) {av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_MIXEDARRAY, found %d\n", v);return -1;}break;}

由《音视频入门基础:FLV专题(9)——Script Tag简介》可以知道,SCRIPTDATAECMAARRAY类型 = 4字节的ECMAArrayLength + Variables数组(ECMA数组) + List Terminator

“case AMF_DATA_TYPE_MIXEDARRAY”的代码块中,首先通过avio_skip函数跳过4个字节,即跳过SCRIPTDATAECMAARRAY类型的4字节ECMAArrayLength。关于avio_skip函数的用法可以参考:《FFmpeg源码:avio_skip函数分析》:

        avio_skip(ioc, 4);     // skip 32-bit max array index

解析SCRIPTDATAECMAARRAY类型的过程中,不通过ECMAArrayLength(该属性指定ECMA数组中的元素个数)判断是否结束解析。而是通过“avio_tell(ioc) < max_pos - 2”(文件位置指针当前位置相对于文件首的偏移 是否小于 所在Tag对应的PreviousTagSize相对于文件首的偏移 - 2)来判断是否要结束解析。通过语句amf_get_string(ioc, str_val, sizeof(str_val) 将Variables数组中某元素的PropertyName属性对应的字符串解析出来,存到str_val数组中:

        while (avio_tell(ioc) < max_pos - 2 &&amf_get_string(ioc, str_val, sizeof(str_val)) > 0)

通过递归,自我调用,解析Variables数组中某元素的PropertyData属性。如果解析失败,返回-1:

            // this is the only case in which we would want a nested// parse to not skip over the objectif (amf_parse_object(s, astream, vstream, str_val, max_pos,depth + 1) < 0)return -1;

宏定义AMF_END_OF_OBJECT的值为9:

#define AMF_END_OF_OBJECT         0x09

读取SCRIPTDATAECMAARRAY类型的List Terminator的最后一个字节,判断其是否等于9。由《音视频入门基础:FLV专题(9)——Script Tag简介》可以知道,List Terminator固定占3字节,值必须为0,0,9,所以List Terminator的最后一个字节必须为9。如果读取到不为9,打印日志:““Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_MIXEDARRAY, found”,amf_parse_object函数返回返回-1表示解析失败:

        v = avio_r8(ioc);if (v != AMF_END_OF_OBJECT) {av_log(s, AV_LOG_ERROR, "Missing AMF_END_OF_OBJECT in AMF_DATA_TYPE_MIXEDARRAY, found %d\n", v);return -1;}

(四)解析onMetaData里面的属性

由《音视频入门基础:FLV专题(9)——Script Tag简介》可以知道,

根据《video_file_format_spec_v10_1.pdf》第84页,FLV文件的元数据对象应该在名称为“onMetadata”的SCRIPTDATA tag(Script Tag)中携带。也就是说FLV文件中存在一个特殊的Script Tag,它的ScriptTagBody中的Name属性中的StringData属性存贮“onMetadata”字符串,而它的ScriptTagBody中的Value属性中的Variables数组中的每个元素都分别为一个元数据属性。用来存贮当前文件的一些基本信息,比如视频和音频的编解码器ID、视频的分辨率、文件大小、文件总时长、创建日期等:

amf_parse_object函数中通过下面代码块将名称为“onMetadata”的Script Tag中的部分属性(duration、videodatarate、audiodatarate等)解析出来,存到s->duration和(FLVContext *)(s->priv_data)中:

        // stream info doesn't live any deeper than the first objectif (depth == 1) {if (amf_type == AMF_DATA_TYPE_NUMBER ||amf_type == AMF_DATA_TYPE_BOOL) {if (!strcmp(key, "duration"))s->duration = num_val * AV_TIME_BASE;else if (!strcmp(key, "videodatarate") &&0 <= (int)(num_val * 1024.0))flv->video_bit_rate = num_val * 1024.0;else if (!strcmp(key, "audiodatarate") &&0 <= (int)(num_val * 1024.0))flv->audio_bit_rate = num_val * 1024.0;else if (!strcmp(key, "framerate")) {flv->framerate = av_d2q(num_val, 1000);if (vstream)vstream->avg_frame_rate = flv->framerate;} else if (flv->trust_metadata) {if (!strcmp(key, "videocodecid") && vpar) {int ret = flv_set_video_codec(s, vstream, num_val, 0);if (ret < 0)return ret;} else if (!strcmp(key, "audiocodecid") && apar) {int id = ((int)num_val) << FLV_AUDIO_CODECID_OFFSET;flv_set_audio_codec(s, astream, apar, id);} else if (!strcmp(key, "audiosamplerate") && apar) {apar->sample_rate = num_val;} else if (!strcmp(key, "audiosamplesize") && apar) {apar->bits_per_coded_sample = num_val;} else if (!strcmp(key, "stereo") && apar) {av_channel_layout_default(&apar->ch_layout, num_val + 1);} else if (!strcmp(key, "width") && vpar) {vpar->width = num_val;} else if (!strcmp(key, "height") && vpar) {vpar->height = num_val;} else if (!strcmp(key, "datastream")) {AVStream *st = create_stream(s, AVMEDIA_TYPE_SUBTITLE);if (!st)return AVERROR(ENOMEM);st->codecpar->codec_id = AV_CODEC_ID_TEXT;}}}if (amf_type == AMF_DATA_TYPE_STRING) {if (!strcmp(key, "encoder")) {int version = -1;if (1 == sscanf(str_val, "Open Broadcaster Software v0.%d", &version)) {if (version > 0 && version <= 655)flv->broken_sizes = 1;}} else if (!strcmp(key, "metadatacreator")) {if (   !strcmp (str_val, "MEGA")|| !strncmp(str_val, "FlixEngine", 10))flv->broken_sizes = 1;}}}

相关文章:

音视频入门基础:FLV专题(13)——FFmpeg源码中,解析任意Type值的SCRIPTDATAVALUE类型的实现

一、SCRIPTDATAVALUE类型 从《音视频入门基础&#xff1a;FLV专题&#xff08;9&#xff09;——Script Tag简介》中可以知道&#xff0c;根据《video_file_format_spec_v10_1.pdf》第80到81页&#xff0c;SCRIPTDATAVALUE类型由一个8位&#xff08;1字节&#xff09;的Type和…...

jvm里的metaspace oom 排查问题思路-使用MAT

文章目录 metapace oom 问题排查点-1-duplicate class 数量排查点-2-classloader 数量一些可能迷惑人的方向 metapace oom 问题 metapace 问题都是由加载的class 太多&#xff0c;导致内存不够报出来的。 排查点-1-duplicate class 数量 首先看这里&#xff0c;通常一个clas…...

2025舜宇招聘【内推码】

【2025内推码】 DSwNQ9yu DSJXN8Mr 舜宇集团2025届全球校园招聘正式启动&#xff01;&#xff01;&#xff01; 专业需求&#xff1a;机械、自动化、电子、电气、通信、控制、测控、计算机、软件、物理、光学等专业&#xff1b; 工作地点&#xff1a;宁波余姚、浙江杭州、广东…...

APP自动化搭建与应用

APP自动化环境搭建 用于做APP端UI自动化&#xff0c;adb连接手机设备。 需要的工具java编辑器&#xff1a;jdk、Android-sdk软件开发工具组、appium的python客户端、nodes.js、夜神模拟器、apk包、uiautomatorviewer 第一步&#xff1a;安装sdk&#xff0c;里面包含建立工具bu…...

kafka-windows集群部署

kafka-windows集群部署目录 文章目录 kafka-windows集群部署目录前言一、复制出来四个kafka文件夹二、修改集群每个kafka的配置文件四、启动zookeeper&#xff0c;kafka集群 前言 部署本文步骤可以先阅读这一篇博客&#xff0c;这篇是关于单机kafka部署测试的。本文用到的文件…...

4个顶级的大模型推理引擎

LLM 在文本生成应用中表现出色&#xff0c;例如具有高理解度和流畅度的聊天和代码完成模型。然而&#xff0c;它们的庞大规模也给推理带来了挑战。基本推理速度很慢&#xff0c;因为 LLM 会逐个生成文本标记&#xff0c;需要对每个下一个标记进行重复调用。随着输入序列的增长&…...

Oracle中ADD_MONTHS()函数详解

文章目录 前言一、ADD_MONTHS()的语法二、主要用途三、测试用例总结 前言 在Oracle数据库中&#xff0c;ADD_MONTHS()函数用于在日期中添加指定的月数。 一、ADD_MONTHS()的语法 ADD_MONTHS(date, n) 其中&#xff0c;date是一个日期值&#xff0c;n是一个整数值&#xff0c…...

【SQL】掌握SQL查询技巧:高效数据整合与查询优化

目录 1. SQL 的基本构成2. SQL 联接&#xff08;JOIN&#xff09;2.1 内联接&#xff08;INNER JOIN&#xff09;2.2 外联接&#xff08;OUTER JOIN&#xff09;2.2.1 左外联接&#xff08;LEFT JOIN&#xff09;2.2.2 右外联接&#xff08;RIGHT JOIN&#xff09;2.2.3 全外联…...

一个月学会Java 第5天 控制结构

Day5 控制结构 这么叫可能有些就算有基础的人也看不懂&#xff0c;其实就是if-else、switch-case、for、while、do-while这几个&#xff0c;没基础的听到了这个也不要慌张&#xff0c;这几个是程序的基础&#xff0c;多多训练就好 第一章 顺序结构 这章其实没有什么好讲的&…...

世界职业院校技能大赛(大数据技术与应用)参赛项目介绍内容模拟示例参考

最近关注世界职业院校技能大赛的同学应该都知道了&#xff0c;比赛已经正式改为”世界职业院校技能大赛“了&#xff0c;不仅仅是名称变化&#xff0c;而且比赛的形式也发生了巨大的改革&#xff0c;2024年世界职业院校技能大赛设置42个赛道&#xff0c;要求各比赛项目提交项目…...

【Python】文件及目录

文章目录 概要一、文件对象的函数1.1 open()函数1.2 文件对象的函数1.3 with语句 二、基于os和os.path模块的目录操作三、基于Pandas的文件处理3.1 Pandas读写各种类型文件 其他章节的内容 概要 本文主要将了打开文件的函数open()的参数&#xff0c;以及文件对象的函数&#x…...

OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3566移植案例(下)

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 概述 OpenHarmony Camera驱动模型结构 HDI Implementation&#x…...

霓虹灯数字时钟(可复制源代码)

文章目录 一、效果演示二、CodeHTMLCSSJavaScript 三、实现思路拆分CSS 部分JavaScript 部分 四、源代码 一、效果演示 文末可一键复制完整代码 二、Code HTML <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><…...

大模型微调技术之 LoRA:开启高效微调新时代

一、LoRA 简介 LoRA&#xff0c;即低秩适应&#xff08;Low-Rank Adaptation&#xff09;&#xff0c;是一种用于微调大型语言模型的技术&#xff0c;旨在以较小的计算资源和数据量实现模型的快速适应特定任务或领域。 LoRA 方法通过引入低秩近似的思想&#xff0c;对大型预训…...

【Vue】Vue2(2)

文章目录 1 数据代理1.1 回顾Object.defineproperty方法1.2 何为数据代理1.3 Vue中的数据代理 2 事件处理2.1 事件的基本使用2.2 事件修饰符2.3 键盘事件 1 数据代理 1.1 回顾Object.defineproperty方法 <!DOCTYPE html> <html><head><meta charset&quo…...

如何实现一个基于 HTML+CSS+JS 的任务进度条

如何实现一个基于 HTMLCSSJS 的任务进度条 在网页开发中&#xff0c;任务进度条是一种常见的 UI 组件&#xff0c;它可以直观地展示任务的完成情况。本文将向你展示如何使用 HTML CSS JavaScript 来创建一个简单的、交互式的任务进度条。用户可以通过点击进度条的任意位置来…...

学会流体力学,冬天洗澡再也不冷啦

前些日子收到一位网友“究极理性怪物”的私信&#xff0c;说最近在学校的公共浴室洗澡时&#xff0c;快被冻死了&#xff0c;希望我从流体力学角度帮他分析一下浴室的温度分布&#xff0c;以便找到相对温暖的洗澡位置。 我看到后觉得很有意思&#xff0c;就与他展开了关于澡堂…...

WPF下使用FreeRedis操作RedisStream实现简单的消息队列

Redis Stream简介 Redis Stream是随着5.0版本发布的一种新的Redis数据类型: 高效消费者组:允许多个消费者组从同一数据流的不同部分消费数据,每个消费者组都能独立地处理消息,这样可以并行处理和提高效率。 阻塞操作:消费者可以设置阻塞操作,这样它们会在流中有新数据…...

踩坑NVTX

最开始在 【简说】NVTX Nsight Nvidia性能分析利器 看到NVTX的时候&#xff0c;我觉得这是一个好东西啊&#xff0c;可以详细说明每一段时间对应的是哪一段程序。 看了一下github&#xff0c;他的文章已经过时&#xff0c;现在已经不需要链接动态库了&#xff0c;直接includ…...

Ubuntu修改IP方法

方法一&#xff1a;通过图形化界面修改IP 打开网络设置&#xff1a; 点击桌面右上角的网络图标&#xff0c;然后选择“设置”或“网络设置”。 选择网络接口&#xff1a; 在网络设置窗口中&#xff0c;选择你正在使用的网络接口&#xff08;有线或无线网络&#xff09;。 进…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

HTML 语义化

目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案&#xff1a; 语义化标签&#xff1a; <header>&#xff1a;页头<nav>&#xff1a;导航<main>&#xff1a;主要内容<article>&#x…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

3403. 从盒子中找出字典序最大的字符串 I

3403. 从盒子中找出字典序最大的字符串 I 题目链接&#xff1a;3403. 从盒子中找出字典序最大的字符串 I 代码如下&#xff1a; class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...