音视频入门基础: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;}}}

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

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

相关文章

动态代理有用吗?一文了解靠谱的动态代理有哪些标准

在当今互联网时代中&#xff0c;从网络安全、隐私保护、市场调研和互联网营销到软件测试、缓存管理和数据库连接&#xff0c;用户为了更好地完成此类工作&#xff0c;往往会使用动态代理&#xff0c;那么进一步了解动态代理、明确动态代理的使用场景和选择标准则是十分有必要的…

OJ在线评测系统 后端微服务架构 注册中心 Nacos入门到启动

注册中心 服务架构中的注册中心是一个关键组件&#xff0c;用于管理和协助微服务之间的通信。注册中心的主要职责是服务的注册和发现&#xff0c;确保各个微服务能够相互找到并进行调用。 主要功能&#xff1a; 服务注册&#xff1a;微服务在启动时&#xff0c;将自身信息&am…

vite学习教程01、vite构建vue2

文章目录 前言一、vite初始化项目二、修改配置文件2.1、修改main.js文件2.2、修改App.vue文件2.3、修改helloworld.vue2.4、修改vite.conf.js2.5、修改vue版本--修改package.json文件 三、安装vue2和vite插件四、启动服务资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝3W&…

常见激活函数总结

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正。 一. 激活函数的定义 激活函数&#xff08;Activation Function&#xff09;是人工神经网络中对每个神经元的输入进行非线性变换的函数。神经网络中的每个神经元都会接受来自上一层的输入&#xf…

Windows安装HeidiSQL教程(图文)

一、软件简介 HeidiSQL是一款开源的数据库管理工具&#xff0c;主要用于管理MySQL、MariaDB、SQL Server、PostgreSQL和SQLite等数据库系统。它提供了直观的用户界面&#xff0c;使用户可以轻松地连接到数据库服务器、执行SQL查询、浏览和编辑数据、管理数据库结构等操作。 跨…

力扣hot100--链表

链表 1. 2. 两数相加 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff…

【word脚注】双栏设置word脚注,脚注仅位于左栏,右栏不留白

【word脚注】双栏设置word脚注&#xff0c;脚注仅位于左栏&#xff0c;右栏不留白 调整前效果解决方法调整后效果参考文献 调整前效果 调整前&#xff1a;脚注位于左下角&#xff0c;但右栏与左栏内容对其&#xff0c;未填充右下角的空白区域 解决方法 备份源文件复制脚注内…

git创建新分支

git创建新分支 1.先在gitLab上New branch. 2.本地右键git小乌 - /切换/检出-创建新分支&#xff0c;分支名称和上一步创建的一样。 最后记得改个文件提交下&#xff0c;看看gitLab上是否提交成功。

蝶形激光器驱动(温控精度0.002°C 激光电流分辨率5uA)

蝶形半导体激光器驱动电流的稳定性直接决定了其输出波长的稳定性,进而影响检测精度.为了满足气体浓度检测中对激光器输出波长稳定可调的要求,设计了数字与模拟电路混合的恒流驱动电路.STM32为主控芯片数控模块完成扫描AD/DA转换;模拟电路主要由负反馈运算放大、高精度CMOS管和反…

22.第二阶段x86游戏实战2-背包遍历REP指令详解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

rtmp协议转websocketflv的去队列积压

websocket server的优点 websocket server的好处&#xff1a;WebSocket 服务器能够实现实时的数据推送&#xff0c;服务器可以主动向客户端发送数据 1 不需要客户端不断轮询。 2 不需要实现httpserver跨域。 在需要修改协议的时候比较灵活&#xff0c;我们发送数据的时候比较…

【网络安全】利用XSS、OAuth配置错误实现token窃取及账户接管 (ATO)

未经许可,不得转载。 文章目录 正文正文 目标:target.com 在子域sub1.target.com上,我发现了一个XSS漏洞。由于针对该子域的漏洞悬赏较低,我希望通过此漏洞将攻击升级至app.target.com,因为该子域的悬赏更高。 分析认证机制后,我发现: sub1.target.com:使用基于Cook…

微信小程序——音乐播放器

一、界面设计 播放页面&#xff1a; 显示当前播放歌曲的封面图片、歌曲名称、歌手名称。有播放 / 暂停按钮、上一首、下一首按钮。进度条显示播放进度&#xff0c;可以拖动进度条调整播放位置。音量调节滑块。 歌曲列表页面&#xff1a; 展示歌曲列表&#xff0c;包括歌曲名称、…

C++——STL简介

目录 一、什么是STL 二、STL的版本 三、STL的六大组件 没用的话..... 不知不觉两个月没写博客了&#xff0c;暑假后期因为学校的事情在忙&#xff0c;开学又在准备学校的java免修&#xff0c;再然后才继续开始学C&#xff0c;然后最近打算继续写博客沉淀一下最近学到的几周…

构建高效团队,内部CRM系统的益处详解

内部CRM系统的最大优势之一是它能够集中并系统化客户信息&#xff0c;包括联系方式、购买历史、偏好设置、服务记录等。这种集中式的数据管理使企业能够快速响应客户需求&#xff0c;预测客户行为&#xff0c;提供个性化的服务或产品。更重要的是&#xff0c;它有助于建立一个统…

【PyTorch】图像分割

图像分割是什么 Image Segmentation 将图像每一个像素分类 图像分割分类 超像素分割&#xff1a;少量超像素代替大量像素&#xff0c;常用于图像预处理语义分割&#xff1a;逐像素分类&#xff0c;无法区分个体实例分割&#xff1a;对个体目标进行分割全景分割&#xff1a;…

信息学奥赛使用的编程IDE:Dev-C++ 安装指南

信息学奥赛&#xff08;NOI&#xff09;作为全国性的编程竞赛&#xff0c;要求参赛学生具备扎实的编程能力&#xff0c;而熟练使用适合的编程工具则是学习与竞赛的基础。在众多编程环境中&#xff0c;Dev-C IDE 因其简洁、轻量、支持C编程等特点&#xff0c;成为许多参赛者的常…

Pikachu-SSRF(curl / file_get_content)

SSRF SSRF是Server-side Request Forge的缩写&#xff0c;中文翻译为服务端请求伪造。产生的原因是由于服务端提供了从其他服务器应用获取数据的功能且没有对地址和协议等做过滤和限制。常见的一个场景就是&#xff0c;通过用户输入的URL来获取图片。这个功能如果被恶意使用&am…

AI先驱荣获2024诺贝尔物理学奖

瑞典皇家科学院10月8日宣布&#xff0c;将2024年诺贝尔物理学奖授予John J. Hopfield和Geoffrey E. Hinton&#xff0c;以表彰他们利用人工神经网络实现机器学习的奠基性发现和发明。 John J. Hopfield&#xff08;约翰J霍普菲尔德&#xff09;美国新泽西州普林斯顿大学 Geoff…

1500元买哪款显卡好?对比一下,差别明显

在游戏过程中&#xff0c;显卡负责渲染游戏画面&#xff0c;将其转化为可视化的图像&#xff0c;并快速显示在屏幕上&#xff0c;确保游戏运行的流畅性和画面的质量。所以对于游戏电脑来说&#xff0c;显卡的重要性尤为突出。虽说在最近几年&#xff0c;显卡市场的“消费升级”…