コード例 #1
0
ファイル: dump_raw.c プロジェクト: HellicarAndLewis/Caravideo
static int raw_on_metadata_tag(flv_tag * tag, amf_data * name, amf_data * data, flv_parser * parser) {
    printf("* Metadata event name: %s\n", amf_string_get_bytes(name));
    printf("* Metadata contents: ");
    amf_data_dump(stdout, data, 0);
    printf("\n");
    return OK;
}
コード例 #2
0
ファイル: dump_raw.c プロジェクト: HellicarAndLewis/Caravideo
/* raw FLV file metadata dump callback */
static int raw_on_metadata_tag_only(flv_tag * tag, amf_data * name, amf_data * data, flv_parser * parser) {
    flvmeta_opts * options = (flvmeta_opts*) parser->user_data;

    if (options->metadata_event == NULL) {
        if (!strcmp((char*)amf_string_get_bytes(name), "onMetaData")) {
            dump_raw_amf_data(data);
            return FLVMETA_DUMP_STOP_OK;
        }
    }
    else {
        if (!strcmp((char*)amf_string_get_bytes(name), options->metadata_event)) {
            dump_raw_amf_data(data);
        }
    }
    return OK;
}
コード例 #3
0
ファイル: dump_xml.c プロジェクト: bygreencn/flvmeta
static int xml_on_metadata_tag(flv_tag * tag, amf_data * name, amf_data * data, flv_parser * parser) {
    printf("    <scriptDataObject name=\"%s\">\n", amf_string_get_bytes(name));
    /* dump AMF data as XML, we start from level 3, meaning 6 indentations characters */
    xml_amf_data_dump(data, 1, 3);
    puts("    </scriptDataObject>");
    return OK;
}
コード例 #4
0
ファイル: amf.c プロジェクト: bygreencn/flvmeta
/* read an associative array */
static amf_data * amf_associative_array_read(amf_read_proc read_proc, void * user_data) {
    amf_data * name;
    amf_data * element;
    uint32_be size;
    byte error_code;
    amf_data * data;

    data = amf_associative_array_new();
    if (data == NULL) {
        return NULL;
    }

    /* we ignore the 32 bits array size marker */
    if (read_proc(&size, sizeof(uint32_be), user_data) < sizeof(uint32_be)) {
        amf_data_free(data);
        return amf_data_error(AMF_ERROR_EOF);
    }

    while(1) {
        name = amf_string_read(read_proc, user_data);
        error_code = amf_data_get_error_code(name);
        if (error_code != AMF_ERROR_OK) {
            /* invalid name: error */
            amf_data_free(name);
            amf_data_free(data);
            return amf_data_error(error_code);
        }

        element = amf_data_read(read_proc, user_data);
        error_code = amf_data_get_error_code(element);

        if (amf_string_get_size(name) == 0 || error_code == AMF_ERROR_END_TAG || error_code == AMF_ERROR_UNKNOWN_TYPE) {
            /* end tag or unknown element: end of data, exit loop */
            amf_data_free(name);
            amf_data_free(element);
            break;
        }
        else if (error_code != AMF_ERROR_OK) {
            amf_data_free(name);
            amf_data_free(data);
            amf_data_free(element);
            return amf_data_error(error_code);
        }

        if (amf_associative_array_add(data, (char *)amf_string_get_bytes(name), element) == NULL) {
            amf_data_free(name);
            amf_data_free(element);
            amf_data_free(data);
            return NULL;
        }
        else {
            amf_data_free(name);
        }
    }

    return data;
}
コード例 #5
0
ファイル: dump_json.c プロジェクト: bygreencn/flvmeta
static int json_on_metadata_tag(flv_tag * tag, amf_data * name, amf_data * data, flv_parser * parser) {
    json_t * root;

    printf("\"scriptDataObject\":{\"name\":\"%s\",\"metadata\":", amf_string_get_bytes(name));
    root = NULL;
    /* dump AMF into JSON */
    amf_to_json(data, &root);
    /* print data */
    json_stream_output(stdout, root);
    /* cleanup */
    json_free_value(&root);
    printf("}");
    return OK;
}
コード例 #6
0
ファイル: amf.c プロジェクト: bygreencn/flvmeta
/* read an object */
static amf_data * amf_object_read(amf_read_proc read_proc, void * user_data) {
    amf_data * name;
    amf_data * element;
    byte error_code;
    amf_data * data;

    data = amf_object_new();
    if (data == NULL) {
        return NULL;
    }

    while (1) {
        name = amf_string_read(read_proc, user_data);
        error_code = amf_data_get_error_code(name);
        if (error_code != AMF_ERROR_OK) {
            /* invalid name: error */
            amf_data_free(name);
            amf_data_free(data);
            return amf_data_error(error_code);
        }

        element = amf_data_read(read_proc, user_data);
        error_code = amf_data_get_error_code(element);
        if (error_code == AMF_ERROR_END_TAG || error_code == AMF_ERROR_UNKNOWN_TYPE) {
            /* end tag or unknown element: end of data, exit loop */
            amf_data_free(name);
            amf_data_free(element);
            break;
        }
        else if (error_code != AMF_ERROR_OK) {
            amf_data_free(name);
            amf_data_free(data);
            amf_data_free(element);
            return amf_data_error(error_code);
        }

        if (amf_object_add(data, (char *)amf_string_get_bytes(name), element) == NULL) {
            amf_data_free(name);
            amf_data_free(element);
            amf_data_free(data);
            return NULL;
        }
        else {
            amf_data_free(name);
        }
    }

    return data;
}
コード例 #7
0
ファイル: amf.c プロジェクト: bygreencn/flvmeta
/* clone AMF data */
amf_data * amf_data_clone(const amf_data * data) {
    /* we copy data recursively */
    if (data != NULL) {
        switch (data->type) {
        case AMF_TYPE_NUMBER:
            return amf_number_new(amf_number_get_value(data));
        case AMF_TYPE_BOOLEAN:
            return amf_boolean_new(amf_boolean_get_value(data));
        case AMF_TYPE_STRING:
            if (data->string_data.mbstr != NULL) {
                return amf_string_new((byte *)strdup((char *)amf_string_get_bytes(data)), amf_string_get_size(data));
            }
            else {
                return amf_str(NULL);
            }
        case AMF_TYPE_NULL:
            return NULL;
        case AMF_TYPE_UNDEFINED:
            return NULL;
        /*case AMF_TYPE_REFERENCE:*/
        case AMF_TYPE_OBJECT:
        case AMF_TYPE_ASSOCIATIVE_ARRAY:
        case AMF_TYPE_ARRAY:
        {
            amf_data * d = amf_data_new(data->type);
            if (d != NULL) {
                amf_list_init(&d->list_data);
                amf_list_clone(&data->list_data, &d->list_data);
            }
            return d;
        }
        case AMF_TYPE_DATE:
            return amf_date_new(amf_date_get_milliseconds(data), amf_date_get_timezone(data));
        /*case AMF_TYPE_SIMPLEOBJECT:*/
        case AMF_TYPE_XML:
            return NULL;
        case AMF_TYPE_CLASS:
            return NULL;
        }
    }
    return NULL;
}
コード例 #8
0
ファイル: dump_json.c プロジェクト: noirotm/flvmeta
/* JSON metadata dumping */
static void json_amf_data_dump(const amf_data * data, json_emitter * je) {
    if (data != NULL) {
        amf_node * node;
        char str[128];

        switch (data->type) {
            case AMF_TYPE_NUMBER:
                json_emit_number(je, data->number_data);
                break;
            case AMF_TYPE_BOOLEAN:
                json_emit_boolean(je, data->boolean_data);
                break;
            case AMF_TYPE_STRING:
                json_emit_string(je, (char *)amf_string_get_bytes(data), amf_string_get_size(data));
                break;
            case AMF_TYPE_OBJECT:
                json_emit_object_start(je);
                node = amf_object_first(data);
                while (node != NULL) {
                    json_emit_object_key(je,
                        (char *)amf_string_get_bytes(amf_object_get_name(node)),
                        amf_string_get_size(amf_object_get_name(node))
                    );
                    json_amf_data_dump(amf_object_get_data(node), je);
                    node = amf_object_next(node);
                }
                json_emit_object_end(je);
                break;
            case AMF_TYPE_NULL:
            case AMF_TYPE_UNDEFINED:
                json_emit_null(je);
                break;
            case AMF_TYPE_ASSOCIATIVE_ARRAY:
                json_emit_object_start(je);
                node = amf_associative_array_first(data);
                while (node != NULL) {
                    json_emit_object_key(je,
                        (char *)amf_string_get_bytes(amf_associative_array_get_name(node)),
                        amf_string_get_size(amf_associative_array_get_name(node))
                    );
                    json_amf_data_dump(amf_object_get_data(node), je);
                    node = amf_associative_array_next(node);
                }
                json_emit_object_end(je);
                break;
            case AMF_TYPE_ARRAY:
                json_emit_array_start(je);
                node = amf_array_first(data);
                while (node != NULL) {
                    json_amf_data_dump(amf_array_get(node), je);
                    node = amf_array_next(node);
                }
                json_emit_array_end(je);
                break;
            case AMF_TYPE_DATE:
                amf_date_to_iso8601(data, str, sizeof(str));
                json_emit_string(je, str, strlen(str));
                break;
            case AMF_TYPE_XML: break;
            case AMF_TYPE_CLASS: break;
            default: break;
        }
    }
}
コード例 #9
0
ファイル: dump_xml.c プロジェクト: bygreencn/flvmeta
/* XML metadata dumping */
static void xml_amf_data_dump(const amf_data * data, int qualified, int indent_level) {
    if (data != NULL) {
        amf_node * node;
        time_t time;
        struct tm * t;
        char datestr[128];
        int markers;
        char * ns;
        char ns_decl[50];

        /* namespace to use whether we're using qualified mode */
        ns = (qualified == 1) ? "amf:" : "";

        /* if indent_level is zero, that means we're at the root of the xml document
           therefore we need to insert the namespace definition */
        if (indent_level == 0) {
            sprintf(ns_decl, " xmlns%s=\"http://schemas.flvmeta.org/AMF0/1.0/\"", ns);
        }
        else {
            strcpy(ns_decl, "");
        }

        /* print indentation spaces */
        printf("%*s", indent_level * 2, "");

        switch (data->type) {
            case AMF_TYPE_NUMBER:
                printf("<%snumber%s value=\"%.12g\"/>\n", ns, ns_decl, data->number_data);
                break;
            case AMF_TYPE_BOOLEAN:
                printf("<%sboolean%s value=\"%s\"/>\n", ns, ns_decl, (data->boolean_data) ? "true" : "false");
                break;
            case AMF_TYPE_STRING:
                if (amf_string_get_size(data) > 0) {
                    printf("<%sstring%s>", ns, ns_decl);
                    /* check whether the string contains xml characters, if so, CDATA it */
                    markers = has_xml_markers((char*)amf_string_get_bytes(data), amf_string_get_size(data));
                    if (markers) {
                        printf("<![CDATA[");
                    }
                    /* do not print more than the actual length of string */
                    printf("%.*s", (int)amf_string_get_size(data), amf_string_get_bytes(data));
                    if (markers) {
                        printf("]]>");
                    }
                    printf("</%sstring>\n", ns);
                }
                else {
                    /* simplify empty xml element into a more compact form */
                    printf("<%sstring%s/>\n", ns, ns_decl);
                }
                break;
            case AMF_TYPE_OBJECT:
                if (amf_object_size(data) > 0) {
                    printf("<%sobject%s>\n", ns, ns_decl);
                    node = amf_object_first(data);
                    while (node != NULL) {
                        printf("%*s<%sentry name=\"%s\">\n", (indent_level + 1) * 2, "", ns, amf_string_get_bytes(amf_object_get_name(node)));
                        xml_amf_data_dump(amf_object_get_data(node), qualified, indent_level + 2);
                        node = amf_object_next(node);
                        printf("%*s</%sentry>\n", (indent_level + 1) * 2, "", ns);
                    }
                    printf("%*s</%sobject>\n", indent_level * 2, "", ns);
                }
                else {
                    /* simplify empty xml element into a more compact form */
                    printf("<%sobject%s/>\n", ns, ns_decl);
                }
                break;
            case AMF_TYPE_NULL:
                printf("<%snull%s/>\n", ns, ns_decl);
                break;
            case AMF_TYPE_UNDEFINED:
                printf("<%sundefined%s/>\n", ns, ns_decl);
                break;
            case AMF_TYPE_ASSOCIATIVE_ARRAY:
                if (amf_associative_array_size(data) > 0) {
                    printf("<%sassociativeArray%s>\n", ns, ns_decl);
                    node = amf_associative_array_first(data);
                    while (node != NULL) {
                        printf("%*s<%sentry name=\"%s\">\n", (indent_level + 1) * 2, "", ns, amf_string_get_bytes(amf_associative_array_get_name(node)));
                        xml_amf_data_dump(amf_associative_array_get_data(node), qualified, indent_level + 2);
                        node = amf_associative_array_next(node);
                        printf("%*s</%sentry>\n", (indent_level + 1) * 2, "", ns);
                    }
                    printf("%*s</%sassociativeArray>\n", indent_level * 2, "", ns);
                }
                else {
                    /* simplify empty xml element into a more compact form */
                    printf("<%sassociativeArray%s/>\n", ns, ns_decl);
                }
                break;
            case AMF_TYPE_ARRAY:
                if (amf_array_size(data) > 0) {
                    printf("<%sarray%s>\n", ns, ns_decl);
                    node = amf_array_first(data);
                    while (node != NULL) {
                        xml_amf_data_dump(amf_array_get(node), qualified, indent_level + 1);
                        node = amf_array_next(node);
                    }
                    printf("%*s</%sarray>\n", indent_level * 2, "", ns);
                }
                else {
                    /* simplify empty xml element into a more compact form */
                    printf("<%sarray%s/>\n", ns, ns_decl);
                }
                break;
            case AMF_TYPE_DATE:
                time = amf_date_to_time_t(data);
                tzset();
                t = localtime(&time);
                strftime(datestr, sizeof(datestr), "%Y-%m-%dT%H:%M:%S", t);
                printf("<%sdate%s value=\"%s\"/>\n", ns, ns_decl, datestr);
                break;
            case AMF_TYPE_XML: break;
            case AMF_TYPE_CLASS: break;
            default: break;
        }
    }
}
コード例 #10
0
ファイル: dump_json.c プロジェクト: bygreencn/flvmeta
/* JSON metadata dumping */
static void amf_to_json(const amf_data * data, json_t ** object) {
    if (data != NULL) {
        json_t * value;
        amf_node * node;
        time_t time;
        struct tm * t;
        char str[128];
        char * escaped_str;

        switch (data->type) {
            case AMF_TYPE_NUMBER:
                sprintf(str, "%.12g", data->number_data);
                *object = json_new_number(str);
                break;
            case AMF_TYPE_BOOLEAN:
                *object = (data->boolean_data) ? json_new_true() : json_new_false();
                break;
            case AMF_TYPE_STRING:
                escaped_str = json_escape((char *)amf_string_get_bytes(data));
                *object = json_new_string(escaped_str);
                free(escaped_str);
                break;
            case AMF_TYPE_OBJECT:
                *object = json_new_object();
                node = amf_object_first(data);
                while (node != NULL) {
                    amf_to_json(amf_object_get_data(node), &value);
                    escaped_str = json_escape((char *)amf_string_get_bytes(amf_object_get_name(node)));
                    json_insert_pair_into_object(*object, escaped_str, value);
                    free(escaped_str);
                    node = amf_object_next(node);
                }
                break;
            case AMF_TYPE_NULL:
            case AMF_TYPE_UNDEFINED:
                *object = json_new_null();
                break;
            case AMF_TYPE_ASSOCIATIVE_ARRAY:
                *object = json_new_object();
                node = amf_associative_array_first(data);
                while (node != NULL) {
                    amf_to_json(amf_associative_array_get_data(node), &value);
                    json_insert_pair_into_object(*object, (const char *)amf_string_get_bytes(amf_associative_array_get_name(node)), value);
                    node = amf_associative_array_next(node);
                }
                break;
            case AMF_TYPE_ARRAY:
                *object = json_new_array();
                node = amf_array_first(data);
                while (node != NULL) {
                    amf_to_json(amf_array_get(node), &value);
                    json_insert_child(*object, value);
                    node = amf_array_next(node);
                }
                break;
            case AMF_TYPE_DATE:
                time = amf_date_to_time_t(data);
                tzset();
                t = localtime(&time);
                strftime(str, sizeof(str), "%Y-%m-%dT%H:%M:%S", t);
                *object = json_new_string(str);
                break;
            case AMF_TYPE_XML: break;
            case AMF_TYPE_CLASS: break;
            default: break;
        }
    }
}
コード例 #11
0
ファイル: check.c プロジェクト: bygreencn/flvmeta
/* check FLV file validity */
int check_flv_file(const flvmeta_opts * opts) {
    flv_stream * flv_in;
    flv_header header;
    int errors, warnings;
    int result;
    char message[256];
    uint32 prev_tag_size, tag_number;
    uint32 last_timestamp, last_video_timestamp, last_audio_timestamp;
    struct stat file_stats;
    int have_audio, have_video;
    flvmeta_opts opts_loc;
    flv_info info;
    int have_desync;
    int have_on_metadata;
    file_offset_t on_metadata_offset;
    amf_data * on_metadata;
    int have_on_last_second;
    uint32 on_last_second_timestamp;

    int have_prev_audio_tag;
    flv_audio_tag prev_audio_tag;
    int have_prev_video_tag;
    flv_video_tag prev_video_tag;

    int video_frames_number, keyframes_number;

    prev_audio_tag = 0;
    prev_video_tag = 0;
    
    have_audio = have_video = 0;
    tag_number = 0;
    last_timestamp = last_video_timestamp = last_audio_timestamp = 0;
    have_desync = 0;
    have_prev_audio_tag = have_prev_video_tag = 0;
    video_frames_number = keyframes_number = 0;
    have_on_metadata = 0;
    on_metadata_offset = 0;
    on_metadata = NULL;
    have_on_last_second = 0;
    on_last_second_timestamp = 0;

    /* file stats */
    if (stat(opts->input_file, &file_stats) != 0) {
        return ERROR_OPEN_READ;
    }

    /* open file for reading */
    flv_in = flv_open(opts->input_file);
    if (flv_in == NULL) {
        return ERROR_OPEN_READ;
    }
    
    errors = warnings = 0;

    report_start(opts);

    /** check header **/

    /* check signature */
    result = flv_read_header(flv_in, &header);
    if (result == FLV_ERROR_EOF) {
        print_fatal("F11001", 0, "unexpected end of file in header");
        goto end;
    }
    else if (result == FLV_ERROR_NO_FLV) {
        print_fatal("F11002", 0, "FLV signature not found in header");
        goto end;
    }

    /* version */
    if (header.version != FLV_VERSION) {
        sprintf(message, "header version should be 1, %d found instead", header.version);
        print_error("E11003", 3, message);
    }

    /* video and audio flags */
    if (!flv_header_has_audio(header) && !flv_header_has_video(header)) {
        print_error("E11004", 4, "header signals the file does not contain video tags or audio tags");
    }
    else if (!flv_header_has_audio(header)) {
        print_info("I11005", 4, "header signals the file does not contain audio tags");
    }
    else if (!flv_header_has_video(header)) {
        print_warning("W11006", 4, "header signals the file does not contain video tags");
    }

    /* reserved flags */
    if (header.flags & 0xFA) {
        print_error("E11007", 4, "header reserved flags are not zero");
    }

    /* offset */
    if (flv_header_get_offset(header) != 9) {
        sprintf(message, "header offset should be 9, %d found instead", flv_header_get_offset(header));
        print_error("E11008", 5, message);
    }

    /** check first previous tag size **/

    result = flv_read_prev_tag_size(flv_in, &prev_tag_size);
    if (result == FLV_ERROR_EOF) {
        print_fatal("F12009", 9, "unexpected end of file in previous tag size");
        goto end;
    }
    else if (prev_tag_size != 0) {
        sprintf(message, "first previous tag size should be 0, %d found instead", prev_tag_size);
        print_error("E12010", 9, message);
    }

    /* we reached the end of file: no tags in file */
    if (flv_get_offset(flv_in) == file_stats.st_size) {
        print_fatal("F10011", 13, "file does not contain tags");
        goto end;
    }

    /** read tags **/
    while (flv_get_offset(flv_in) < file_stats.st_size) {
        flv_tag tag;
        file_offset_t offset;
        uint32 body_length, timestamp, stream_id;
        int decr_timestamp_signaled;

        result = flv_read_tag(flv_in, &tag);
        if (result != FLV_OK) {
            print_fatal("F20012", flv_get_offset(flv_in), "unexpected end of file in tag");
            goto end;
        }

        ++tag_number;

        offset = flv_get_current_tag_offset(flv_in);
        body_length = flv_tag_get_body_length(tag);
        timestamp = flv_tag_get_timestamp(tag);
        stream_id = flv_tag_get_stream_id(tag);

        /* check tag type */
        if (tag.type != FLV_TAG_TYPE_AUDIO
            && tag.type != FLV_TAG_TYPE_VIDEO
            && tag.type != FLV_TAG_TYPE_META
        ) {
            sprintf(message, "unknown tag type %hhd", tag.type);
            print_error("E30013", offset, message);
        }

        /* check consistency with global header */
        if (!have_video && tag.type == FLV_TAG_TYPE_VIDEO) {
            if (!flv_header_has_video(header)) {
                print_warning("W11014", offset, "video tag found despite header signaling the file contains no video");
            }
            have_video = 1;
        }
        if (!have_audio && tag.type == FLV_TAG_TYPE_AUDIO) {
            if (!flv_header_has_audio(header)) {
                print_warning("W11015", offset, "audio tag found despite header signaling the file contains no audio");
            }
            have_audio = 1;
        }

        /* check body length */
        if (body_length > (file_stats.st_size - flv_get_offset(flv_in))) {
            sprintf(message, "tag body length (%d bytes) exceeds file size", body_length);
            print_fatal("F20016", offset + 1, message);
            goto end;
        }
        else if (body_length > MAX_ACCEPTABLE_TAG_BODY_LENGTH) {
            sprintf(message, "tag body length (%d bytes) is abnormally large", body_length);
            print_warning("W20017", offset + 1, message);
        }
        else if (body_length == 0) {
            print_warning("W20018", offset + 1, "tag body length is zero");
        }

        /** check timestamp **/
        decr_timestamp_signaled = 0;

        /* check whether first timestamp is zero */
        if (tag_number == 1 && timestamp != 0) {
            sprintf(message, "first timestamp should be zero, %d found instead", timestamp);
            print_error("E40019", offset + 4, message);
        }

        /* check whether timestamps decrease in a given stream */
        if (tag.type == FLV_TAG_TYPE_AUDIO) {
            if (last_audio_timestamp > timestamp) {
                sprintf(message, "audio tag timestamps are decreasing from %d to %d", last_audio_timestamp, timestamp);
                print_error("E40020", offset + 4, message);
            }
            last_audio_timestamp = timestamp;
            decr_timestamp_signaled = 1;
        }
        if (tag.type == FLV_TAG_TYPE_VIDEO) {
            if (last_video_timestamp > timestamp) {
                sprintf(message, "video tag timestamps are decreasing from %d to %d", last_video_timestamp, timestamp);
                print_error("E40021", offset + 4, message);
            }
            last_video_timestamp = timestamp;
            decr_timestamp_signaled = 1;
        }

        /* check for overflow error */
        if (last_timestamp > timestamp && last_timestamp - timestamp > 0xF00000) {
            print_error("E40022", offset + 4, "extended bits not used after timestamp overflow");
        }

        /* check whether timestamps decrease globally */
        else if (!decr_timestamp_signaled && last_timestamp > timestamp && last_timestamp - timestamp >= 1000) {
            sprintf(message, "timestamps are decreasing from %d to %d", last_timestamp, timestamp);
            print_error("E40023", offset + 4, message);
        }

        last_timestamp = timestamp;

        /* check for desyncs between audio and video: one second or more is suspicious */
        if (have_video && have_audio && !have_desync && labs(last_video_timestamp - last_audio_timestamp) >= 1000) {
            sprintf(message, "audio and video streams are desynchronized by %ld ms",
                labs(last_video_timestamp - last_audio_timestamp));
            print_warning("W40024", offset + 4, message);
            have_desync = 1; /* do not repeat */
        }

        /** stream id must be zero **/
        if (stream_id != 0) {
            sprintf(message, "tag stream id must be zero, %d found instead", stream_id);
            print_error("E20025", offset + 8, message);
        }

        /* check tag body contents only if not empty */
        if (body_length > 0) {

            /** check audio info **/
            if (tag.type == FLV_TAG_TYPE_AUDIO) {
                flv_audio_tag at;
                uint8_bitmask audio_format;

                result = flv_read_audio_tag(flv_in, &at);
                if (result == FLV_ERROR_EOF) {
                    print_fatal("F20012", offset + 11, "unexpected end of file in tag");
                    goto end;
                }

                /* check whether the format varies between tags */
                if (have_prev_audio_tag && prev_audio_tag != at) {
                    print_warning("W51026", offset + 11, "audio format changed since last tag");
                }

                /* check format */
                audio_format = flv_audio_tag_sound_format(at);
                if (audio_format == 12 || audio_format == 13) {
                    sprintf(message, "unknown audio format %d", audio_format);
                    print_warning("W51027", offset + 11, message);
                }
                else if (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_G711_A
                    || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_G711_MU
                    || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_RESERVED
                    || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_MP3_8
                    || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_DEVICE_SPECIFIC
                ) {
                    sprintf(message, "audio format %d is reserved for internal use", audio_format);
                    print_warning("W51028", offset + 11, message);
                }

                /* check consistency, see flash video spec */
                if (flv_audio_tag_sound_rate(at) != FLV_AUDIO_TAG_SOUND_RATE_44
                    && audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_AAC
                ) {
                    print_warning("W51029", offset + 11, "audio data in AAC format should have a 44KHz rate, field will be ignored");
                }

                if (flv_audio_tag_sound_type(at) == FLV_AUDIO_TAG_SOUND_TYPE_STEREO
                    && (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER
                        || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_16_MONO
                        || audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_NELLYMOSER_8_MONO)
                ) {
                    print_warning("W51030", offset + 11, "audio data in Nellymoser format cannot be stereo, field will be ignored");
                }

                else if (flv_audio_tag_sound_type(at) == FLV_AUDIO_TAG_SOUND_TYPE_MONO
                    && audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_AAC
                ) {
                    print_warning("W51031", offset + 11, "audio data in AAC format should be stereo, field will be ignored");
                }

                else if (audio_format == FLV_AUDIO_TAG_SOUND_FORMAT_LINEAR_PCM) {
                    print_warning("W51032", offset + 11, "audio data in Linear PCM, platform endian format should not be used because of non-portability");
                }

                prev_audio_tag = at;
                have_prev_audio_tag = 1;
            }
            /** check video info **/
            else if (tag.type == FLV_TAG_TYPE_VIDEO) {
                flv_video_tag vt;
                uint8_bitmask video_frame_type, video_codec;

                video_frames_number++;

                result = flv_read_video_tag(flv_in, &vt);
                if (result == FLV_ERROR_EOF) {
                    print_fatal("F20012", offset + 11, "unexpected end of file in tag");
                    goto end;
                }

                /* check whether the format varies between tags */
                if (have_prev_video_tag && flv_video_tag_codec_id(prev_video_tag) != flv_video_tag_codec_id(vt)) {
                    print_warning("W60033", offset + 11, "video format changed since last tag");
                }

                /* check video frame type */
                video_frame_type = flv_video_tag_frame_type(vt);
                if (video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME
                    && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_INTERFRAME
                    && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_DISPOSABLE_INTERFRAME
                    && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_GENERATED_KEYFRAME
                    && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_COMMAND_FRAME
                ) {
                    sprintf(message, "unknown video frame type %d", video_frame_type);
                    print_error("E60034", offset + 11, message);
                }

                if (video_frame_type == FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) {
                    keyframes_number++;
                }

                /* check whether first frame is a keyframe */
                if (!have_prev_video_tag && video_frame_type != FLV_VIDEO_TAG_FRAME_TYPE_KEYFRAME) {
                    print_warning("W60035", offset + 11, "first video frame is not a keyframe, playback will suffer");
                }

                /* check video codec */
                video_codec = flv_video_tag_codec_id(vt);
                if (video_codec != FLV_VIDEO_TAG_CODEC_JPEG
                    && video_codec != FLV_VIDEO_TAG_CODEC_SORENSEN_H263
                    && video_codec != FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO
                    && video_codec != FLV_VIDEO_TAG_CODEC_ON2_VP6
                    && video_codec != FLV_VIDEO_TAG_CODEC_ON2_VP6_ALPHA
                    && video_codec != FLV_VIDEO_TAG_CODEC_SCREEN_VIDEO_V2
                    && video_codec != FLV_VIDEO_TAG_CODEC_AVC
                ) {
                    sprintf(message, "unknown video codec id %d", video_codec);
                    print_error("E61034", offset + 11, message);
                }

                /* according to spec, JPEG codec is not currently used */
                if (video_codec == FLV_VIDEO_TAG_CODEC_JPEG) {
                    print_warning("W61035", offset + 11, "JPEG codec not currently used");
                }

                prev_video_tag = vt;
                have_prev_video_tag = 1;
            }
            /** check script data info **/
            else if (tag.type == FLV_TAG_TYPE_META) {
                amf_data * name;
                amf_data * data;

                result = flv_read_metadata(flv_in, &name, &data);

                if (result == FLV_ERROR_EOF) {
                    print_fatal("F20012", offset + 11, "unexpected end of file in tag");
                    amf_data_free(name);
                    amf_data_free(data);
                    goto end;
                }
                else if (result == FLV_ERROR_EMPTY_TAG) {
                    print_warning("W70038", offset + 11, "empty metadata tag");
                }
                else if (result == FLV_ERROR_INVALID_METADATA_NAME) {
                    print_error("E70039", offset + 11, "invalid metadata name");
                }
                else if (result == FLV_ERROR_INVALID_METADATA) {
                    print_error("E70039", offset + 11, "invalid metadata");
                }
                else if (amf_data_get_type(name) != AMF_TYPE_STRING) {
                    /* name type checking */
                    sprintf(message, "invalid metadata name type: %d, should be a string (2)", amf_data_get_type(name));
                    print_error("E70038", offset, message);
                }
                else {
                    /* empty name checking */
                    if (amf_string_get_size(name) == 0) {
                        print_warning("W70038", offset, "empty metadata name");
                    }

                    /* check whether all body size has been read */
                    if (flv_in->current_tag_body_length > 0) {
                        sprintf(message, "%d bytes not read in tag body after metadata end", body_length - flv_in->current_tag_body_length);
                        print_warning("W70040", flv_get_offset(flv_in), message);
                    }
                    else if (flv_in->current_tag_body_length < 0) {
                        sprintf(message, "%d bytes missing from tag body after metadata end", flv_in->current_tag_body_length - body_length);
                        print_warning("W70041", flv_get_offset(flv_in), message);
                    }

                    /* onLastSecond checking */
                    if (!strcmp((char*)amf_string_get_bytes(name), "onLastSecond")) {
                        if (have_on_last_second == 0) {
                            have_on_last_second = 1;
                            on_last_second_timestamp = timestamp;
                        }
                        else {
                            print_warning("W70038", offset, "duplicate onLastSecond event");
                        }
                    }

                    /* onMetaData checking */
                    if (!strcmp((char*)amf_string_get_bytes(name), "onMetaData")) {
                        if (have_on_metadata == 0) {
                            have_on_metadata = 1;
                            on_metadata_offset = offset;
                            on_metadata = amf_data_clone(data);

                            /* check onMetadata type */
                            if (amf_data_get_type(on_metadata) != AMF_TYPE_ASSOCIATIVE_ARRAY) {
                                sprintf(message, "invalid onMetaData data type: %d, should be an associative array (8)", amf_data_get_type(on_metadata));
                                print_error("E70038", offset, message);
                            }

                            /* onMetaData must be the first tag at 0 timestamp */
                            if (tag_number != 1) {
                                print_warning("W70038", offset, "onMetadata event found after the first tag");
                            }
                            if (timestamp != 0) {
                                print_warning("W70038", offset, "onMetadata event found after timestamp zero");
                            }
                        }
                        else {
                            print_warning("W70038", offset, "duplicate onMetaData event");
                        }
                    }

                    /* unknown metadata name */
                    if (strcmp((char*)amf_string_get_bytes(name), "onMetaData")
                    && strcmp((char*)amf_string_get_bytes(name), "onCuePoint")
                    && strcmp((char*)amf_string_get_bytes(name), "onLastSecond")) {
                        sprintf(message, "unknown metadata event name: '%s'", (char*)amf_string_get_bytes(name));
                        print_info("I70039", flv_get_offset(flv_in), message);
                    }
                }

                amf_data_free(name);
                amf_data_free(data);
            }
        }

        /* check body length against previous tag size */
        result = flv_read_prev_tag_size(flv_in, &prev_tag_size);
        if (result != FLV_OK) {
            print_fatal("F12036", flv_get_offset(flv_in), "unexpected end of file after tag");
            goto end;
        }

        if (prev_tag_size != FLV_TAG_SIZE + body_length) {
            sprintf(message, "previous tag size should be %d, %d found instead", FLV_TAG_SIZE + body_length, prev_tag_size);
            print_error("E12037", flv_get_offset(flv_in), message);
            goto end;
        }
    }

    /** final checks */

    /* check consistency with global header */
    if (!have_video && flv_header_has_video(header)) {
        print_warning("W11038", 4, "no video tag found despite header signaling the file contains video");
    }
    if (!have_audio && flv_header_has_audio(header)) {
        print_warning("W11039", 4, "no audio tag found despite header signaling the file contains audio");
    }

    /* check last timestamps */
    if (have_video && have_audio && labs(last_audio_timestamp - last_video_timestamp) >= 1000) {
        if (last_audio_timestamp > last_video_timestamp) {
            sprintf(message, "video stops %d ms before audio", last_audio_timestamp - last_video_timestamp);
            print_warning("W40040", file_stats.st_size, message);
        }
        else {
            sprintf(message, "audio stops %d ms before video", last_video_timestamp - last_audio_timestamp);
            print_warning("W40041", file_stats.st_size, message);
        }
    }

    /* check video keyframes */
    if (have_video && keyframes_number == 0) {
        print_warning("W60042", file_stats.st_size, "no keyframe detected, file is probably broken or incomplete");
    }
    if (have_video && keyframes_number == video_frames_number) {
        print_warning("W60043", file_stats.st_size, "only keyframes detected, probably inefficient compression scheme used");
    }

    /* only keyframes + onLastSecond bug */
    if (have_video && have_on_last_second && keyframes_number == video_frames_number) {
        print_warning("W60044", file_stats.st_size, "only keyframes detected and onLastSecond event present, file is probably not playable");
    }

    /* check onLastSecond timestamp */
    if (have_on_last_second && (last_timestamp - on_last_second_timestamp) >= 2000) {
        sprintf(message, "onLastSecond event located %d ms before the last tag", last_timestamp - on_last_second_timestamp);
        print_warning("W70050", file_stats.st_size, message);
    }

    /* check onMetaData presence */
    if (!have_on_metadata) {
        print_warning("W70044", file_stats.st_size, "onMetaData event not found, file might not be playable");
    }
    else {
        amf_node * n;
        int have_width, have_height;

        have_width = 0;
        have_height = 0;

        /* compute metadata */
        opts_loc.verbose = 0;
        opts_loc.reset_timestamps = 0;
        opts_loc.preserve_metadata = 0;
        opts_loc.all_keyframes = 0;
        opts_loc.error_handling = FLVMETA_IGNORE_ERRORS;

        flv_reset(flv_in);
        if (get_flv_info(flv_in, &info, &opts_loc) != OK) {
            print_fatal("F10042", 0, "unable to compute metadata");
            goto end;
        }

        /* more metadata checks */
        for (n = amf_associative_array_first(on_metadata); n != NULL; n = amf_associative_array_next(n)) {
            byte * name;
            amf_data * data;
            byte type;

            name = amf_string_get_bytes(amf_associative_array_get_name(n));
            data = amf_associative_array_get_data(n);
            type = amf_data_get_type(data);

            /* hasMetadata (bool): true */
            if (!strcmp((char*)name, "hasMetadata")) {
                if (type == AMF_TYPE_BOOLEAN) {
                    if (amf_boolean_get_value(data) == 0) {
                        print_warning("W70045", on_metadata_offset, "hasMetadata should be set to true");
                    }
                }
                else {
                    sprintf(message, "Invalid type for hasMetadata: expected %s, got %s",
                        get_amf_type_string(AMF_TYPE_BOOLEAN),
                        get_amf_type_string(type));
                    print_warning("W70046", on_metadata_offset, message);
                }
            }

            /* hasVideo (bool) */
            if (!strcmp((char*)name, "hasVideo")) {
                if (type == AMF_TYPE_BOOLEAN) {
                    if (amf_boolean_get_value(data) != info.have_video) {
                        sprintf(message, "hasVideo should be set to %s", info.have_video ? "true" : "false");
                        print_warning("W70045", on_metadata_offset, message);
                    }
                }
                else {
                    sprintf(message, "Invalid type for hasVideo: expected %s, got %s",
                        get_amf_type_string(AMF_TYPE_BOOLEAN),
                        get_amf_type_string(type));
                    print_warning("W70046", on_metadata_offset, message);
                }
            }

            /* hasAudio (bool) */
            if (!strcmp((char*)name, "hasAudio")) {
                if (type == AMF_TYPE_BOOLEAN) {
                    if (amf_boolean_get_value(data) != info.have_audio) {
                        sprintf(message, "hasAudio should be set to %s", info.have_audio ? "true" : "false");
                        print_warning("W70045", on_metadata_offset, message);
                    }
                }
                else {
                    sprintf(message, "Invalid type for hasAudio: expected %s, got %s",
                        get_amf_type_string(AMF_TYPE_BOOLEAN),
                        get_amf_type_string(type));
                    print_warning("W70046", on_metadata_offset, message);
                }
            }

            /* duration (number) */
            if (!strcmp((char*)name, "duration")) {
                if (type == AMF_TYPE_NUMBER) {
                    number64 duration, file_duration;
                    if (info.have_audio) {
                        duration = (info.last_timestamp - info.first_timestamp + info.audio_frame_duration) / 1000.0;
                    }
                    else {
                        duration = (info.last_timestamp - info.first_timestamp + info.video_frame_duration) / 1000.0;
                    }
                    file_duration = amf_number_get_value(data);

                    if (fabs(file_duration - duration) > 1.0) {
                        sprintf(message, "duration should be %.12g, got %.12g", duration, file_duration);
                        print_warning("W70045", on_metadata_offset, message);
                    }
                }
                else {
                    sprintf(message, "Invalid type for duration: expected %s, got %s",
                        get_amf_type_string(AMF_TYPE_NUMBER),
                        get_amf_type_string(type));
                    print_warning("W70046", on_metadata_offset, message);
                }
            }

            /* lasttimestamp: (number) */
            /* lastkeyframetimestamp: (number) */
            /* width: (number) */
            /* height: (number) */
            /* videodatarate: (number)*/
            /* framerate: (number) */
            /* audiodatarate: (number) */
            /* audiosamplerate: (number) */
            /* audiosamplesize: (number) */
            /* stereo: (boolean) */
            /* filesize: (number) */
            /* videosize: (number) */
            /* audiosize: (number) */
            /* datasize: (number) */
            /* audiocodecid: (number) */
            /* videocodecid: (number) */
            /* audiodelay: (number) */
            /* canSeekToEnd: (boolean) */
            /* hasKeyframes: (boolean) */
            /* keyframes: (object) */
        }

        /* missing width or height can cause size problem in various players */
        if (!have_width) {
            print_error("E60047", on_metadata_offset, "width information not found in metadata, problems might occur in some players");
        }
        if (!have_height) {
            print_error("E60047", on_metadata_offset, "height information not found in metadata, problems might occur in some players");
        }
    }

    /* could we compute video resolution ? */
    if (info.video_width == 0 && info.video_height == 0) {
        print_warning("W60044", file_stats.st_size, "unable to determine video resolution");
    }

end:
    report_end(opts, errors, warnings);
    
    amf_data_free(on_metadata);
    flv_close(flv_in);
    
    return (errors > 0) ? ERROR_INVALID_FLV_FILE : OK;
}