END_TEST START_TEST(test_flv_tag_set_timestamp_short) { flv_tag tag; uint32 val; flv_tag_set_timestamp(&tag, 0x00112233); val = uint24_be_to_uint32(tag.timestamp); fail_if(val != 0x00112233, "expected 0x00112233, got 0x%X", val); fail_if(tag.timestamp_extended != 0x00, "expected 0x00, got 0x%hhX", tag.timestamp_extended); }
END_TEST START_TEST(test_flv_tag_set_timestamp_extended) { flv_tag tag; uint32 val; flv_tag_set_timestamp(&tag, 0x44332211); val = uint24_be_to_uint32(tag.timestamp); fail_if(val != 0x00332211, "expected 0x00332211, got 0x%X", val); fail_if(tag.timestamp_extended != 0x44, "expected 0x44, got 0x%hhX", tag.timestamp_extended); }
/* 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[]) { FlvHeader header; FlvTag tag; byte* tagData; FILE *fpdst = NULL, *fpsrc = NULL; int i = 0, srccount = argc - 2, headerLength, duration_index = 0, prevSize, dataSize, offset, foundduration = 0, zero = 0, basetimestamp[2], lasttimestamp[2] = {0}; char** src = argv + 2; double duration = 0.0; int bts = 0; if (argc < 2) { fprintf(stderr, "Usage: %s flvtobesaved 1stflv [2ndflv [3rdflv [...]]]\n", argv[0]); exit(1); } if ((fpdst = fopen(argv[1], "wb")) == NULL) { fprintf(stderr, "Can't write to file '%s'\n", argv[1]); exit(1); } while (i < srccount) { if ((fpsrc = fopen(src[i], "rb")) == NULL) { fprintf(stderr, "Can't open file '%s'\n", src[i]); exit(1); } if(! flv_header_read(fpsrc, &header) || ! flv_is_valid_header(&header)) { fprintf(stderr, "The header of file '%s' is broken or is not FLV header.\n", src[i]); exit(1); } if (i == 0) { fwrite(&header, sizeof(FlvHeader), 1, fpdst); fwrite(&zero, sizeof(int), 1, fpdst); // the first previous tag size is 0 duration_index = sizeof(FlvHeader); } headerLength = intval(header.headerLength, 4); if (0 != fseek(fpsrc, headerLength+4, SEEK_SET)) { // skip to real flv tag data(skip the first previous tag size, +4) fprintf(stderr, "The first previousSize(should be 0) of file '%s' is broken.\n", src[i]); exit(1); } bts = (int)(duration * 1000); basetimestamp[0] = lasttimestamp[0]; basetimestamp[1] = lasttimestamp[1]; if (bts < basetimestamp[0]) bts = basetimestamp[0]; if (bts < basetimestamp[1]) bts = basetimestamp[1]; foundduration = 0; while (tagData = flv_tag_read(fpsrc, &tag, &dataSize, &prevSize)) { if (tag.tagType == 0x12 && ! foundduration) { // if script data and duration not found, try to get duration duration += flv_tag_get_duration(tagData, dataSize, &offset); foundduration = 1; if (i == 0) { // prepare the script data for writing, we choose the first FLV file header as sample duration_index += 4 + sizeof(FlvTag) + offset; flv_scriptdata_strip_keyframes(&tag, tagData, &dataSize); flv_tag_write(fpdst, &tag, tagData, &dataSize, &prevSize); } } else if (tag.tagType == 0x8 || tag.tagType == 0x9) { lasttimestamp[tag.tagType - 0x8] = bts + flv_tag_get_timestamp(&tag); flv_tag_set_timestamp(&tag, lasttimestamp[tag.tagType - 0x8]); flv_tag_write(fpdst, &tag, tagData, &dataSize, &prevSize); if (i == 0 && ! foundduration) { duration_index += 4 + sizeof(FlvTag) + dataSize; } } } //fprintf(stdout, "base: %d, last: %d\n", basetimestamp[0], lasttimestamp[0]); printf("completely merging file '%s' to '%s'\n", src[i], argv[1]); fclose(fpsrc); ++i; } if (0 != fseek(fpdst, duration_index, SEEK_SET)) quit("can't seek to duration\n", 1); fwrite(bytevaldouble(duration), 1, 8, fpdst); // save real duration to file fclose(fpdst); return 0; }