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

cpp 复制代码
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");
            else
                add_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 space
    case AMF_DATA_TYPE_MIXEDARRAY:
    {
        unsigned v;
        avio_skip(ioc, 4);     // skip 32-bit max array index
        while (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 object
            if (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 skip
        av_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 object
        if (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 object

        if ((!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 timezone
            avpriv_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:

cpp 复制代码
#define MAX_DEPTH 16      ///< arbitrary limit to prevent unbounded recursion

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

cpp 复制代码
    if (depth > MAX_DEPTH)
        return AVERROR_PATCHWELCOME;

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

cpp 复制代码
    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为枚举变量:

cpp 复制代码
    AMFDataType amf_type;
    //...    
    amf_type = avio_r8(ioc);

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

cpp 复制代码
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的方法:

cpp 复制代码
    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 space
    case AMF_DATA_TYPE_MIXEDARRAY:
    //...
        break;
    }
    case AMF_DATA_TYPE_ARRAY:
    //...
    break;
    case AMF_DATA_TYPE_DATE:
    //...
    default:                    // unsupported type, we couldn't skip
        av_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的实现》:

cpp 复制代码
    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的实现》:

cpp 复制代码
    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类型:

cpp 复制代码
    case AMF_DATA_TYPE_MIXEDARRAY:
    {
        unsigned v;
        avio_skip(ioc, 4);     // skip 32-bit max array index
        while (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 object
            if (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函数分析》:

cpp 复制代码
        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数组中:

cpp 复制代码
        while (avio_tell(ioc) < max_pos - 2 &&
               amf_get_string(ioc, str_val, sizeof(str_val)) > 0)

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

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

宏定义AMF_END_OF_OBJECT的值为9:

cpp 复制代码
#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表示解析失败:

cpp 复制代码
        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)中:

cpp 复制代码
        // stream info doesn't live any deeper than the first object
        if (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;
                }
            }
        }
相关推荐
Eric.Lee202113 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
却道天凉_好个秋19 分钟前
音视频学习(二十四):hls协议
音视频·hls
-Mr_X-2 小时前
windows下srs流媒体服务器使用ffmpeg推流
ffmpeg
dvlinker3 小时前
C++开源项目 VLC 源代码的交叉编译以及库的裁剪方法详解
ffmpeg·mingw-w64·msys2·cygwin·开源vlc·vlc编译·vlc裁剪
darkdragonking13 小时前
FLV视频封装格式详解
音视频
元争栈道14 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
元争栈道16 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
因我你好久不见18 小时前
springboot java ffmpeg 视频压缩、提取视频帧图片、获取视频分辨率
java·spring boot·ffmpeg
MediaTea20 小时前
Pr:音频仪表
音视频
桃园码工20 小时前
13_HTML5 Audio(音频) --[HTML5 API 学习之旅]
音视频·html5·audio