diff --git a/ChangeLog b/ChangeLog index f46e73a80..7aa847d7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2005-09-18 Moritz Bunkus + + * mkvmerge: new feature: MPEG-4 part 2 streams are searched for + the pixel width/height values. Those are taken if they differ from + those values in the source container. This is a work-around for + buggy muxers, e.g. broken video camera firmwares writing bad MP4 + files. Fixes bug 149. + 2005-09-15 Moritz Bunkus * mkvmerge: bug fix: Appending files with RealVideo was diff --git a/src/common/common.cpp b/src/common/common.cpp index 5da1d3742..f448b3613 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -1012,6 +1012,18 @@ round_to_nearest_pow2(uint32_t value) { return best_value; } +int +int_log2(uint32_t value) { + uint32_t highest, bit, mask; + + highest = 0; + for (mask = 1, bit = 0; 31 >= bit; mask <<= 1, ++bit) + if (value & mask) + highest = bit; + + return highest; +} + bool parse_int(const char *s, int64_t &value) { diff --git a/src/common/common.h b/src/common/common.h index 1bf586f56..55c1ca92e 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -281,6 +281,8 @@ bool MTX_DLL_API parse_double(const char *s, double &value); int MTX_DLL_API get_arg_len(const char *fmt, ...); int MTX_DLL_API get_varg_len(const char *fmt, va_list ap); +int MTX_DLL_API int_log2(uint32_t value); + extern int MTX_DLL_API verbose; #define foreach(it, vec) for (it = (vec).begin(); it != (vec).end(); it++) diff --git a/src/common/mpeg4_common.cpp b/src/common/mpeg4_common.cpp index 4533158c7..be590c315 100644 --- a/src/common/mpeg4_common.cpp +++ b/src/common/mpeg4_common.cpp @@ -26,17 +26,9 @@ #include "mm_io.h" #include "mpeg4_common.h" -bool -mpeg4_p2_extract_par_internal(const unsigned char *buffer, - int buffer_size, - uint32_t &par_num, - uint32_t &par_den) { - const uint32_t ar_nums[16] = {0, 1, 12, 10, 16, 40, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0}; - const uint32_t ar_dens[16] = {1, 1, 11, 11, 11, 33, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1}; - uint32_t marker, aspect_ratio_info, num, den; - bit_cursor_c bits(buffer, buffer_size); +static bool +mpeg4_p2_find_vol_header(bit_cursor_c &bits) { + uint32_t marker; while (!bits.eof()) { marker = bits.peek_bits(32); @@ -52,37 +44,151 @@ mpeg4_p2_extract_par_internal(const unsigned char *buffer, continue; } - mxverb(2, "mpeg4 AR: found VOL header at %u\n", - bits.get_bit_position() / 8); - bits.skip_bits(32); + return true; + } - // VOL header - bits.skip_bits(1); // random access - bits.skip_bits(8); // vo_type - if (bits.get_bit()) { // is_old_id - bits.skip_bits(4); // vo_ver_id - bits.skip_bits(3); // vo_priority + return false; +} + +static bool +mpeg4_p2_extract_size_internal(const unsigned char *buffer, + int buffer_size, + uint32_t &width, + uint32_t &height) { + bit_cursor_c bits(buffer, buffer_size); + int shape, time_base_den; + + if (!mpeg4_p2_find_vol_header(bits)) + return false; + + mxverb(2, "mpeg4 size: found VOL header at %u\n", + bits.get_bit_position() / 8); + bits.skip_bits(32); + + // VOL header + bits.skip_bits(1); // random access + bits.skip_bits(8); // vo_type + if (bits.get_bit()) { // is_old_id + bits.skip_bits(4); // vo_ver_id + bits.skip_bits(3); // vo_priority + } + + if (15 == bits.get_bits(4)) // ASPECT_EXTENDED + bits.skip_bits(16); + + if (1 == bits.get_bit()) { // vol control parameter + bits.skip_bits(2); // chroma format + bits.skip_bits(1); // low delay + if (1 == bits.get_bit()) { // vbv parameters + bits.skip_bits(15 + 1); // first half bitrate, marker + bits.skip_bits(15 + 1); // latter half bitrate, marker + bits.skip_bits(15 + 1); // first half vbv buffer size, marker + bits.skip_bits(3); // latter half vbv buffer size + bits.skip_bits(11 + 1); // first half vbv occupancy, marker + bits.skip_bits(15 + 1); // latter half vbv oocupancy, marker } + } - aspect_ratio_info = bits.get_bits(4); - mxverb(2, "mpeg4 AR: aspect_ratio_info: %u\n", aspect_ratio_info); - if (aspect_ratio_info == 15) { // ASPECT_EXTENDED - num = bits.get_bits(8); - den = bits.get_bits(8); - } else { - num = ar_nums[aspect_ratio_info]; - den = ar_dens[aspect_ratio_info]; - } - mxverb(2, "mpeg4 AR: %u den: %u\n", num, den); + shape = bits.get_bits(2); + if (3 == shape) // GRAY_SHAPE + bits.skip_bits(4); // video object layer shape extension - if ((num != 0) && (den != 0) && ((num != 1) || (den != 1)) && - (((float)num / (float)den) != 1.0)) { - par_num = num; - par_den = den; + bits.skip_bits(1); // marker + + time_base_den = bits.get_bits(16); // time base den + bits.skip_bits(1); // marker + if (1 == bits.get_bit()) { // fixed vop rate + int time_increment_bits = int_log2(time_base_den - 1) + 1; + bits.skip_bits(time_increment_bits); // time base num + } + + if (0 == shape) { // RECT_SHAPE + uint32_t tmp_width, tmp_height; + + bits.skip_bits(1); + tmp_width = bits.get_bits(13); + bits.skip_bits(1); + tmp_height = bits.get_bits(13); + + if ((0 != tmp_width) && (0 != tmp_height)) { + width = tmp_width; + height = tmp_height; return true; } + } + + return false; +} + +/** Extract the widht and height from a MPEG4 video frame + + This function searches a buffer containing a MPEG4 video frame + for the width and height. + + \param buffer The buffer containing the MPEG4 video frame. + \param buffer_size The size of the buffer in bytes. + \param width The width, if found, is stored in this variable. + \param height The height, if found, is stored in this variable. + + \return \c true if width and height were found and \c false + otherwise. +*/ +bool +mpeg4_p2_extract_size(const unsigned char *buffer, + int buffer_size, + uint32_t &width, + uint32_t &height) { + try { + return mpeg4_p2_extract_size_internal(buffer, buffer_size, width, height); + } catch (...) { return false; } +} + +static bool +mpeg4_p2_extract_par_internal(const unsigned char *buffer, + int buffer_size, + uint32_t &par_num, + uint32_t &par_den) { + const uint32_t ar_nums[16] = {0, 1, 12, 10, 16, 40, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + const uint32_t ar_dens[16] = {1, 1, 11, 11, 11, 33, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1}; + uint32_t aspect_ratio_info, num, den; + bit_cursor_c bits(buffer, buffer_size); + + if (!mpeg4_p2_find_vol_header(bits)) + return false; + + mxverb(2, "mpeg4 AR: found VOL header at %u\n", + bits.get_bit_position() / 8); + bits.skip_bits(32); + + // VOL header + bits.skip_bits(1); // random access + bits.skip_bits(8); // vo_type + if (bits.get_bit()) { // is_old_id + bits.skip_bits(4); // vo_ver_id + bits.skip_bits(3); // vo_priority + } + + aspect_ratio_info = bits.get_bits(4); + mxverb(2, "mpeg4 AR: aspect_ratio_info: %u\n", aspect_ratio_info); + if (aspect_ratio_info == 15) { // ASPECT_EXTENDED + num = bits.get_bits(8); + den = bits.get_bits(8); + } else { + num = ar_nums[aspect_ratio_info]; + den = ar_dens[aspect_ratio_info]; + } + mxverb(2, "mpeg4 AR: %u den: %u\n", num, den); + + if ((num != 0) && (den != 0) && ((num != 1) || (den != 1)) && + (((float)num / (float)den) != 1.0)) { + par_num = num; + par_den = den; + return true; + } return false; } diff --git a/src/common/mpeg4_common.h b/src/common/mpeg4_common.h index 528be3f2a..a059780d6 100644 --- a/src/common/mpeg4_common.h +++ b/src/common/mpeg4_common.h @@ -141,6 +141,9 @@ struct video_frame_t { bool MTX_DLL_API mpeg4_p2_extract_par(const unsigned char *buffer, int buffer_size, uint32_t &par_num, uint32_t &par_den); +bool MTX_DLL_API mpeg4_p2_extract_size(const unsigned char *buffer, + int buffer_size, + uint32_t &width, uint32_t &height); void MTX_DLL_API mpeg4_p2_find_frame_types(const unsigned char *buffer, int buffer_size, vector &frames); diff --git a/src/output/p_video.cpp b/src/output/p_video.cpp index 5bc647b61..3d5e461b8 100644 --- a/src/output/p_video.cpp +++ b/src/output/p_video.cpp @@ -358,7 +358,8 @@ mpeg4_p2_video_packetizer_c(generic_reader_c *_reader, video_packetizer_c(_reader, MKV_V_MPEG4_ASP, _fps, _width, _height, _ti), timecodes_generated(0), last_i_p_frame(0), previous_timecode(0), aspect_ratio_extracted(false), input_is_native(_input_is_native), - output_is_native(hack_engaged(ENGAGE_NATIVE_MPEG4)) { + output_is_native(hack_engaged(ENGAGE_NATIVE_MPEG4)), + size_extracted(false) { if (input_is_native) output_is_native = true; @@ -381,6 +382,8 @@ mpeg4_p2_video_packetizer_c(generic_reader_c *_reader, int mpeg4_p2_video_packetizer_c::process(packet_cptr packet) { + if (!size_extracted) + extract_size(packet->memory->data, packet->memory->size); if (!aspect_ratio_extracted) extract_aspect_ratio(packet->memory->data, packet->memory->size); @@ -671,6 +674,29 @@ mpeg4_p2_video_packetizer_c::extract_aspect_ratio(const unsigned char *buffer, aspect_ratio_extracted = true; } +void +mpeg4_p2_video_packetizer_c::extract_size(const unsigned char *buffer, + int size) { + uint32_t width, height; + + if (mpeg4_p2_extract_size(buffer, size, width, height)) { + size_extracted = true; + if ((width != hvideo_pixel_width) || (height != hvideo_pixel_height)) { + set_video_pixel_width(width); + set_video_pixel_height(height); + generic_packetizer_c::set_headers(); + rerender_track_headers(); + mxinfo("Track %lld of '%s': The extracted values for video width and " + "height from the MPEG4 layer 2 video data bitstream differ from " + "what the values in the source container. The ones from the " + "video data bitstream (%ux%u) will be used.\n", + (int64_t)ti.id, ti.fname.c_str(), width, height); + } + + } else if (50 <= frames_output) + aspect_ratio_extracted = true; +} + // ---------------------------------------------------------------- mpeg4_p10_video_packetizer_c:: diff --git a/src/output/p_video.h b/src/output/p_video.h index 75a05dfbe..b24a68887 100644 --- a/src/output/p_video.h +++ b/src/output/p_video.h @@ -80,6 +80,7 @@ protected: deque available_timecodes, available_durations; int64_t timecodes_generated, last_i_p_frame, previous_timecode; bool aspect_ratio_extracted, input_is_native, output_is_native; + bool size_extracted; public: mpeg4_p2_video_packetizer_c(generic_reader_c *_reader, @@ -95,6 +96,7 @@ protected: virtual void flush_frames_maybe(frame_type_e next_frame); virtual void flush_frames(bool end_of_file); virtual void extract_aspect_ratio(const unsigned char *buffer, int size); + virtual void extract_size(const unsigned char *buffer, int size); virtual void fix_codec_string(); virtual void handle_missing_timecodes(bool end_of_file); };