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 } }
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; }
/* 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; }
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; }
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 }
/* 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; }