コード例 #1
0
ファイル: flv-parser.c プロジェクト: zhenyouluo/flv-parser
int flv_parser_run() {
    flv_tag_t *tag;
    flv_read_header();

    for (; ;) {
        tag = flv_read_tag(); // read the tag
        if (!tag) {
            return 0;
        }
        flv_free_tag(tag); // and free it
    }

}
コード例 #2
0
ファイル: flv+srt.c プロジェクト: Fishrock123/obs-studio
int main (int argc, char** argv)
{
    flvtag_t tag;
    FILE* flv = flv_open_read (argv[1]);
    FILE* out = flv_open_write (argv[3]);

    int has_audio, has_video;
    flvtag_init (&tag);

    if (!flv_read_header (flv,&has_audio,&has_video)) {
        fprintf (stderr,"%s is not an flv file\n", argv[1]);
        return EXIT_FAILURE;
    }

    srt_t* head = srt_from_file (argv[2]);
    srt_t* srt = head;

    if (! head) {
        fprintf (stderr,"%s is not an srt file\n", argv[2]);
        return EXIT_FAILURE;
    }

    flv_write_header (out,has_audio,has_video);

    while (flv_read_tag (flv,&tag)) {
        // TODO handle B freame!
        if (srt && flvtag_pts_seconds (&tag) >= srt->timestamp && flvtag_avcpackettype_nalu == flvtag_avcpackettype (&tag)) {
            fprintf (stderr,"%f: %s\n", srt->timestamp, srt_data (srt));
            flvtag_addcaption (&tag, srt_data (srt));
            srt = srt->next;
        }

        flv_write_tag (out,&tag);
        // Write tag out here
    }

    srt_free (head);
    return EXIT_SUCCESS;
}
コード例 #3
0
ファイル: update.c プロジェクト: GarfieldLinux/flvmeta
/*
    Write the flv output file
*/
static int write_flv(flv_stream * flv_in, FILE * flv_out, const flv_info * info, const flv_metadata * meta, const flvmeta_opts * opts) {
    uint32_be size;
    uint32 on_metadata_name_size;
    uint32 on_metadata_size;
    uint32 prev_timestamp_video;
    uint32 prev_timestamp_audio;
    uint32 prev_timestamp_meta;
    uint8 timestamp_extended_video;
    uint8 timestamp_extended_audio;
    uint8 timestamp_extended_meta;
    byte * copy_buffer;
    flv_tag ft, omft;
    int have_on_last_second;

    if (opts->verbose) {
        fprintf(stdout, "Writing %s...\n", opts->output_file);
    }

    /* write the flv header */
    if (flv_write_header(flv_out, &info->header) != 1) {
        return ERROR_WRITE;
    }

    /* first "previous tag size" */
    size = swap_uint32(0);
    if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) {
        return ERROR_WRITE;
    }

    /* create the onMetaData tag */
    on_metadata_name_size = (uint32)amf_data_size(meta->on_metadata_name);
    on_metadata_size = (uint32)amf_data_size(meta->on_metadata);

    omft.type = FLV_TAG_TYPE_META;
    omft.body_length = uint32_to_uint24_be(on_metadata_name_size + on_metadata_size);
    flv_tag_set_timestamp(&omft, 0);
    omft.stream_id = uint32_to_uint24_be(0);
    
    /* write the computed onMetaData tag first if it doesn't exist in the input file */
    if (info->on_metadata_size == 0) {
        if (flv_write_tag(flv_out, &omft) != 1
        || amf_data_file_write(meta->on_metadata_name, flv_out) < on_metadata_name_size
        || amf_data_file_write(meta->on_metadata, flv_out) < on_metadata_size) {
            return ERROR_WRITE;
        }

        /* previous tag size */
        size = swap_uint32(FLV_TAG_SIZE + on_metadata_name_size + on_metadata_size);
        if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) {
            return ERROR_WRITE;
        }
    }

    /* extended timestamp initialization */
    prev_timestamp_video = 0;
    prev_timestamp_audio = 0;
    prev_timestamp_meta = 0;
    timestamp_extended_video = 0;
    timestamp_extended_audio = 0;
    timestamp_extended_meta = 0;

    /* copy the tags verbatim */
    flv_reset(flv_in);

    copy_buffer = (byte *)malloc(info->biggest_tag_body_size + FLV_TAG_SIZE);
    have_on_last_second = 0;
    while (flv_read_tag(flv_in, &ft) == FLV_OK) {
        file_offset_t offset;
        uint32 body_length;
        uint32 timestamp;

        offset = flv_get_current_tag_offset(flv_in);
        body_length = flv_tag_get_body_length(ft);
        timestamp = flv_tag_get_timestamp(ft);

        /* extended timestamp fixing */
        if (ft.type == FLV_TAG_TYPE_META) {
            if (timestamp < prev_timestamp_meta
            && prev_timestamp_meta - timestamp > 0xF00000) {
                ++timestamp_extended_meta;
            }
            prev_timestamp_meta = timestamp;
            if (timestamp_extended_meta > 0) {
                timestamp += timestamp_extended_meta << 24;
            }
        }
        else if (ft.type == FLV_TAG_TYPE_AUDIO) {
            if (timestamp < prev_timestamp_audio
            && prev_timestamp_audio - timestamp > 0xF00000) {
                ++timestamp_extended_audio;
            }
            prev_timestamp_audio = timestamp;
            if (timestamp_extended_audio > 0) {
                timestamp += timestamp_extended_audio << 24;
            }
        }
        else if (ft.type == FLV_TAG_TYPE_VIDEO) {
            if (timestamp < prev_timestamp_video
            && prev_timestamp_video - timestamp > 0xF00000) {
                ++timestamp_extended_video;
            }
            prev_timestamp_video = timestamp;
            if (timestamp_extended_video > 0) {
                timestamp += timestamp_extended_video << 24;
            }
        }

        /* non-zero starting timestamp handling */
        if (opts->reset_timestamps && timestamp > 0) {
            timestamp -= info->first_timestamp;
        }

        flv_tag_set_timestamp(&ft, timestamp);

        /* if we're at the offset of the first onMetaData tag in the input file,
           we write the one we computed instead, discarding the old one */
        if (info->on_metadata_offset == offset) {
            if (flv_write_tag(flv_out, &omft) != 1
            || amf_data_file_write(meta->on_metadata_name, flv_out) < on_metadata_name_size
            || amf_data_file_write(meta->on_metadata, flv_out) < on_metadata_size) {
                free(copy_buffer);
                return ERROR_WRITE;
            }

            /* previous tag size */
            size = swap_uint32(FLV_TAG_SIZE + on_metadata_name_size + on_metadata_size);
            if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) {
                free(copy_buffer);
                return ERROR_WRITE;
            }
        }
        else {
            size_t read_body;
            
            /* insert an onLastSecond metadata tag */
            if (opts->insert_onlastsecond && !have_on_last_second && !info->have_on_last_second && (info->last_timestamp - timestamp) <= 1000) {
                flv_tag tag;
                uint32 on_last_second_name_size = (uint32)amf_data_size(meta->on_last_second_name);
                uint32 on_last_second_size = (uint32)amf_data_size(meta->on_last_second);
                tag.type = FLV_TAG_TYPE_META;
                tag.body_length = uint32_to_uint24_be(on_last_second_name_size + on_last_second_size);
                tag.timestamp = ft.timestamp;
                tag.timestamp_extended = ft.timestamp_extended;
                tag.stream_id = uint32_to_uint24_be(0);
                if (flv_write_tag(flv_out, &tag) != 1
                || amf_data_file_write(meta->on_last_second_name, flv_out) < on_last_second_name_size
                || amf_data_file_write(meta->on_last_second, flv_out) < on_last_second_size) {
                    free(copy_buffer);
                    return ERROR_WRITE;
                }

                /* previous tag size */
                size = swap_uint32(FLV_TAG_SIZE + on_last_second_name_size + on_last_second_size);
                if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) {
                    free(copy_buffer);
                    return ERROR_WRITE;
                }

                have_on_last_second = 1;
            }

            /* if the tag is bigger than expected, it means that
               it's an unknown tag type. In this case, we only
               copy as much data as the copy buffer can contain */
            if (body_length > info->biggest_tag_body_size) {
                body_length = info->biggest_tag_body_size;
            }

            /* copy the tag verbatim */
            read_body = flv_read_tag_body(flv_in, copy_buffer, body_length);
            if (read_body < body_length) {
                /* we have reached end of file on an incomplete tag */
                if (opts->error_handling == FLVMETA_EXIT_ON_ERROR) {
                    free(copy_buffer);
                    return ERROR_EOF;
                }
                else if (opts->error_handling == FLVMETA_FIX_ERRORS) {
                    /* the tag is bogus, just omit it,
                       even though it will make the whole file length
                       calculation wrong, and the metadata inaccurate */
                    /* TODO : fix it by handling that problem in the first pass */
                    free(copy_buffer);
                    return OK;
                }
                else if (opts->error_handling == FLVMETA_IGNORE_ERRORS) {
                    /* just copy the whole tag and exit */
                    flv_write_tag(flv_out, &ft);
                    fwrite(copy_buffer, 1, read_body, flv_out);
                    free(copy_buffer);
                    size = swap_uint32(FLV_TAG_SIZE + read_body);
                    fwrite(&size, sizeof(uint32_be), 1, flv_out);
                    return OK;
                }
            }
            if (flv_write_tag(flv_out, &ft) != 1
            || fwrite(copy_buffer, 1, body_length, flv_out) < body_length) {
                free(copy_buffer);
                return ERROR_WRITE;
            }

            /* previous tag length */
            size = swap_uint32(FLV_TAG_SIZE + body_length);
            if (fwrite(&size, sizeof(uint32_be), 1, flv_out) != 1) {
                free(copy_buffer);
                return ERROR_WRITE;
            }
        }        
    }

    if (opts->verbose) {
        fprintf(stdout, "%s successfully written\n", opts->output_file);
    }

    free(copy_buffer);
    return OK;
}
コード例 #4
0
ファイル: flv+srt.c プロジェクト: szatmary/libcaption
int main(int argc, char** argv)
{
    flvtag_t tag;
    srt_t* old_srt = NULL;
    srt_cue_t* next_cue = NULL;
    double timestamp, offset = 0, clear_timestamp = 0;
    int has_audio, has_video;
    FILE* flv = flv_open_read(argv[1]);
    int fd = open(argv[2], O_RDWR);
    FILE* out = flv_open_write(argv[3]);

    flvtag_init(&tag);

    if (!flv_read_header(flv, &has_audio, &has_video)) {
        fprintf(stderr, "%s is not an flv file\n", argv[1]);
        return EXIT_FAILURE;
    }

    flv_write_header(out, has_audio, has_video);

    fprintf(stderr, "Reading flv from %s\n", argv[1]);
    fprintf(stderr, "Reading captons from %s\n", argv[2]);
    fprintf(stderr, "Writing flv to %s\n", argv[3]);

    while (flv_read_tag(flv, &tag)) {

        srt_t* cur_srt = srt_from_fd(fd);
        timestamp = flvtag_pts_seconds(&tag);

        if (cur_srt) {
            fprintf(stderr, "Loaded new SRT at time %f\n", timestamp);
            if (old_srt != NULL) {
                srt_free(old_srt);
            }
            old_srt = cur_srt;
            offset = timestamp;
            clear_timestamp = timestamp;
            next_cue = cur_srt->cue_head;
        }

        if (flvtag_avcpackettype_nalu == flvtag_avcpackettype(&tag)) {
            if (next_cue && (offset + next_cue->timestamp) <= timestamp) {
                fprintf(stderr, "T: %0.02f (%0.02fs):\n%s\n", (offset + next_cue->timestamp), next_cue->duration, srt_cue_data(next_cue));
                clear_timestamp = (offset + next_cue->timestamp) + next_cue->duration;
                flvtag_addcaption_text(&tag, srt_cue_data(next_cue));
                next_cue = next_cue->next;
            } else if (0 <= clear_timestamp && clear_timestamp <= timestamp) {
                fprintf(stderr, "T: %0.02f: [CAPTIONS CLEARED]\n", timestamp);
                flvtag_addcaption_text(&tag, NULL);
                clear_timestamp = -1;
            }
        }

        flv_write_tag(out, &tag);
    }

    srt_free(old_srt);
    flvtag_free(&tag);
    flv_close(flv);
    flv_close(out);
    return EXIT_SUCCESS;
}
コード例 #5
0
ファイル: main.c プロジェクト: lubing521/delay
void write_cb(struct ev_loop *loop, struct ev_io *w, int revents)
{ 
    if (EV_ERROR & revents)
    {
        perror(" w got invalid event");
        return;
    }
	client *session = (client*) (((char*)w) - offsetof(client, ev_write));
        if (session->fd == 0 )
        { 
            return;
        }
        switch (session->state) {
        
            case CREATED: {
                const char *head;
		char meta[255];
		int meta_fd;
		ssize_t size;
		char meta_data[2048];

		sprintf(meta, "%s/%s/flv", session->g_path, session->name);
		if (access(meta, R_OK) == 0) {
		    head = flv_head;
                    session->media= FLV;
                }
		else {
		    sprintf(meta, "%s/%s/ts", session->g_path, session->name);
		    head = ts_head;
                    session->media= TS ;
		}
                //printf("%s\n", meta);
		meta_fd = open(meta, O_RDONLY);
                if (meta_fd) {
		    size = read(meta_fd, meta_data, sizeof(meta_data));
		    close(meta_fd);
                } else
                {
                    head = error_head;
                }
		if (revents & EV_WRITE){
		    //write(w->fd, head, strlen(head));
                    ssize_t len = safe_send(w->fd, (void*)head, strlen(head));
                    if (len < 0)  {
                       if (errno == EAGAIN)
                           break;
                       else {
			       ev_io_stop(work_loop, &session->ev_write);
			       ev_io_stop(work_loop, &session->ev_read);
                           if (w->fd) {
                               close(w->fd);
                               session->fd = 0;
                           }
                           break;
                       }
                    }
                    //channel not found
                    if (head == error_head) {
			ev_io_stop(work_loop, &session->ev_write);
			ev_io_stop(work_loop, &session->ev_read);
                        if (w->fd) {
                            close(w->fd);
                            session->fd = 0;
                        }
                        break;
                    }
                    len = safe_send(w->fd, meta_data, size);
                    if (len < 0)  {
                       if (errno == EAGAIN)
                           break;
                       else {
			       ev_io_stop(work_loop, &session->ev_write);
			       ev_io_stop(work_loop, &session->ev_read);
                               if (w->fd) {
                                   close(w->fd);
                                   session->fd = 0;
                               }
                           break;
                       }
                    }
		}
                session->state = SEEK;
                break;
            }
            case SEEK: {
                char timestr [64];
                char indexstr [64];
                char fullpath[255];
                int sec;
                if (session->media == TS) {
                    struct tm start;
                    localtime_r(&session->start_time, &start);
                    sec = start.tm_sec % 10;
                    start.tm_sec = start.tm_sec - sec;
                    session->start_time -= sec;
                    strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S.ts", &start);
                    strftime(indexstr, sizeof(timestr), "%Y%m%d%H%M%S.index", &start);
                }else {
                    struct tm start;
                    localtime_r(&session->start_time, &start);
                    sec = start.tm_sec;
                    strftime(timestr, sizeof(timestr), "%Y%m%d%H%M.flv", &start);
                    strftime(indexstr, sizeof(timestr), "%Y%m%d%H%M.index", &start);
                }
                //printf("seek %s\n", timestr);
                off_t pos = 0;
                int fd;
                sprintf(fullpath, "%s/%s/%s", session->g_path, session->name, indexstr);
                fd = open(fullpath, O_RDONLY);
                if (fd > 0) {
                    lseek(fd, 4 * sec, SEEK_SET);
                    read(fd, &pos, sizeof(pos));
                    close(fd);
                }
                sprintf(fullpath, "%s/%s/%s", session->g_path, session->name, timestr);
                //printf("%s\n", fullpath);
                session->file_fd = open(fullpath, O_RDONLY);
                if (session->file_fd < 0) {
                    printf("no channel %s\n", fullpath);
		    ev_io_stop(work_loop, &session->ev_write);
		    ev_io_stop(work_loop, &session->ev_read);
		    close(w->fd);
                    session->fd = 0;
                    break;
                }
                if (pos > 0)
                    lseek(session->file_fd, pos, SEEK_SET);
                session->state = RUNNING;
                break;
            }
            case RUNNING: {
                int interval = 0;
                if (session->media == TS) { //ts
                    unsigned char buffer[TSPACKET_SIZE * 7];
                    unsigned char *p ;
                    uint32_t has_pcr = 0;
                    uint64_t pcr = 0;
                    int64_t diff_time = 0;
                    int64_t diff_pcr = 0;
                    ssize_t len = read(session->file_fd, buffer, sizeof(buffer));
                    if (len <= 0) {
                        ///////////////////////////
                        close(session->file_fd);
                        struct tm start;
                        char timestr[64];
                        char fullpath[255];
                        session->start_time += 10;
                        localtime_r(&session->start_time, &start);
                        strftime(timestr, sizeof(timestr), "%Y%m%d%H%M%S.ts", &start);
                        sprintf(fullpath, "%s/%s/%s", session->g_path, session->name, timestr);

                        session->file_fd = open(fullpath, O_RDONLY);
                        if (session->file_fd < 0)
                        {
                            if (session->cfg_start_time != 0) {
                                 session->start_time = session->cfg_start_time - 10;
                            }
                            else
                                 session->start_time += 10;
                        }
                        session->inited_time = 0;
                        break;
                    }
                    int i;
                    for (i = 0; i < 7; i++)
                    {
                       if ((i * TSPACKET_SIZE) >= len)
                           break;
                       p = buffer + i * TSPACKET_SIZE;
                       if (TSPACKET_HAS_ADAPTATION(p))
                       {
                           if (TSPACKET_GET_ADAPTATION_LEN(p) > 0)
                           {
                               if (TSPACKET_IS_PCRFLAG_SET(p)) {
                                  has_pcr  = 1;
                                   pcr = (TSPACKET_GET_PCRBASE(p)) / 45 ;
                                   if (session->inited_time > 0) {
                                       diff_pcr = pcr - session->last_pcr;
                                       session->last_pcr = pcr;
                                   } else {
                                       session->play_duration = 0;
                                       session->send_duration = 0;
                                       session->inited_time = 1;
                                       session->last_pcr = pcr;
                                       session->last_time = now_time();
                                   }
                               }
                           } 
                       }
                    }

                    len = safe_send(w->fd, buffer, len);
                    if (len < 0)  {
                        if (errno == EAGAIN)
                            break;
                        else {
		                 ev_io_stop(work_loop, &session->ev_write);
		                 ev_io_stop(work_loop, &session->ev_read);
		                 ev_timer_stop (work_loop, &session->ev_time);
                            if (w->fd) {
                               close(w->fd);
                               session->fd = 0;
                            }
                            if (session->file_fd) {
                               close(session->file_fd);
                               session->file_fd = 0;
                            }
                            break;
                        }
                   }
                   if (has_pcr == 0){
                       break;
                   }
                   uint64_t now = now_time();
                   diff_time = now - session->last_time;
                   session->last_time = now;
                   if (diff_pcr < 0) {
                       session->play_duration += 40;
                   }
                   if (diff_time < 0) {
                       session->send_duration += 40;
                   }
                   session->play_duration += diff_pcr;
                   session->send_duration += diff_time;
                   interval = session->play_duration - session->send_duration;
                   if (interval <= 0) {
                       break;
                   }
                   if (interval >= 2) {
                       ev_io_stop(work_loop, w);
                       float c = interval / 1000.0;
                       ev_timer_set(&session->ev_time, c, 0.);
                       ev_timer_start (work_loop, &session->ev_time);
                   }
                  
                }
                else { //flv
                ////////////////////////////////////
                    Tag_s tag;
                    int64_t diff_time = 0;
                    int64_t diff_pcr = 0;
                    uint32_t has_pcr = 0;
                    ssize_t len = flv_read_tag(&tag, session->file_fd);
                    if (len <= 0) {
                        ///////////////////////////
                        close(session->file_fd);
                        struct tm start;
                        char timestr[64];
                        char fullpath[255];
                        session->start_time += 60;
                        localtime_r(&session->start_time, &start);
                        strftime(timestr, sizeof(timestr), "%Y%m%d%H%M.flv", &start);
                        sprintf(fullpath, "%s/%s/%s", session->g_path, session->name, timestr);
                        session->file_fd = open(fullpath, O_RDONLY);
                        printf("change file %s\n", fullpath);
                        if (session->file_fd < 0) {
                            session->start_time -= 60;
                            session->inited_time = 0;
                        }
                        break;
                    }
                    if (IS_VIDEO_TAG(tag)) {
                        has_pcr = 1;
                    if (session->inited_time > 0) {
                        diff_pcr =  tag.time_stamp - session->last_pcr;
                        session->last_pcr = tag.time_stamp;
                    } else {
                        //reinit
                        session->play_duration = 0;
                        session->send_duration = 0;
                        session->inited_time = 1;
                        session->last_time = now_time();
                        session->last_pcr = tag.time_stamp;
                    }
                    }

                    len = safe_send(w->fd, tag.data, 15 + len);
                    if (len < 0)  {
                        free(tag.data);
                        if (errno == EAGAIN)
                            break; 
                        else {
                            ev_io_stop(work_loop, w);
		            ev_io_stop(work_loop, &session->ev_read);
		            ev_timer_stop (work_loop, &session->ev_time);
                            if (session->file_fd) {
                               close(session->file_fd);
                               session->file_fd = 0;
                            }
                            if (w->fd) {
		                close(w->fd);
                                session->fd = 0;
                            }
                            break;
                        }
                   }
                   free(tag.data);
                   if (has_pcr == 0){
                       break;
                   }
                   uint64_t now = now_time();
                   diff_time = now - session->last_time;
                   session->last_time = now;
                   if (diff_pcr < 0) {
                       session->play_duration += 40;
                   }
                   if (diff_time < 0) {
                       session->send_duration += 40;
                   }

                   session->play_duration += diff_pcr;
                   session->send_duration += diff_time;
                   interval = session->play_duration - session->send_duration;
                   if (interval <= 0) {
                       break;
                   }
                   if (interval >= 2) {
                       ev_io_stop(work_loop, w);
                       float c = interval / 1000.0;
                       ev_timer_set(&session->ev_time, c, 0.);
                       ev_timer_start (work_loop, &session->ev_time);
                   }
                }
                break;
            }
            default:
                printf("state error\n");
        } 
         	//close(w->fd);
//	此处可以安装一个ev_timer ev_timer的回调中,再次安装ev_io write
}
コード例 #6
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;
}