diff --git a/src/common/mpeg4_common.cpp b/src/common/mpeg4_common.cpp index b9bb0eab4..f884c9a18 100644 --- a/src/common/mpeg4_common.cpp +++ b/src/common/mpeg4_common.cpp @@ -1,4 +1,4 @@ -/* +/** MPEG video helper functions (MPEG 1, 2 and 4) * mkvmerge -- utility for splicing together matroska files * from component media subtypes * @@ -6,11 +6,10 @@ * see the file COPYING for details * or visit http://www.gnu.org/copyleft/gpl.html * - * $Id$ + * \file + * \version $Id$ * - * MPEG4 video helper functions - * - * Written by Moritz Bunkus . + * \author Written by Moritz Bunkus . */ #include "os.h" @@ -18,6 +17,20 @@ #include "common.h" #include "mpeg4_common.h" +/** Extract the pixel aspect ratio from a MPEG4 video frame + * + * This function searches a buffer containing a MPEG4 video frame + * for the pixel aspectc ratio. If it is found then the numerator + * and the denominator are returned. + * + * \param buffer The buffer containing the MPEG4 video frame. + * \param size The size of the buffer in bytes. + * \param par_num The numerator, if found, is stored in this variable. + * \param par_den The denominator, if found, is stored in this variable. + * + * \return \c true if the pixel aspect ratio was found and \c false + * otherwise. + */ bool mpeg4_extract_par(const unsigned char *buffer, int size, @@ -82,8 +95,22 @@ mpeg4_extract_par(const unsigned char *buffer, return false; } +/** Find frame boundaries and frame types in a packed video frame + * + * This function searches a buffer containing one or more MPEG4 video frames + * for the frame boundaries and their types. This may be the case for B frames + * if they're glued to another frame like they are in AVI files. + * + * \param buffer The buffer containing the MPEG4 video frame(s). + * \param size The size of the buffer in bytes. + * \param frames The data for each frame that is found is put into this + * variable. See ::video_frame_t + * + * \return Nothing. If no frames were found (e.g. only the dummy header for + * a dummy frame) then \a frames will contain no elements. + */ void -mpeg4_find_frame_types(unsigned char *buf, +mpeg4_find_frame_types(const unsigned char *buf, int size, vector &frames) { bit_cursor_c bits(buf, size); @@ -108,7 +135,7 @@ mpeg4_find_frame_types(unsigned char *buf, mxverb(3, "mpeg4_frames: found start code at %d\n", bits.get_bit_position() / 8); bits.skip_bits(32); - if (marker == VOP_START_CODE) { + if (marker == MPEGVIDEO_OBJECT_PLAIN_START_CODE) { if (!bits.get_bits(2, frame_type)) break; if (!first_frame) { @@ -141,3 +168,77 @@ mpeg4_find_frame_types(unsigned char *buf, fit++; } } + +/** \brief Extract the FPS from a MPEG video sequence header + * + * This function looks for a MPEG sequence header in a buffer containing + * a MPEG1 or MPEG2 video frame. If such a header is found its + * FPS index is extracted and returned. This index can be mapped to the + * actual number of frames per second with the function + * ::mpeg_video_get_fps + * + * \param buffer The buffer to search for the header. + * \param size The buffer size. + * + * \return The index or \c -1 if no MPEG sequence header was found or + * if the buffer was too small. + */ +int +mpeg1_2_extract_fps_idx(const unsigned char *buffer, + int size) { + uint32_t marker; + int idx; + + mxverb(3, "mpeg_video_fps: start search in %d bytes\n", size); + if (size < 8) { + mxverb(3, "mpeg_video_fps: sequence header too small\n"); + return -1; + } + marker = get_uint32_be(buffer); + idx = 4; + while ((idx < size) && (marker != MPEGVIDEO_SEQUENCE_START_CODE)) { + marker <<= 8; + marker |= buffer[idx]; + idx++; + } + if (idx >= size) { + mxverb(3, "mpeg_video_fps: no sequence header start code found\n"); + return -1; + } + + mxverb(3, "mpeg_video_fps: found sequence header start code at %d\n", + idx - 4); + idx += 3; // width and height + if (idx >= size) { + mxverb(3, "mpeg_video_fps: sequence header too small\n"); + return -1; + } + return buffer[idx] & 0x0f; +} + +/** \brief Get the number of frames per second + * + * Converts the index returned by ::mpeg_video_extract_fps_idx to a number. + * + * \param idx The index as to convert. + * + * \return The number of frames per second or \c -1.0 if the index was + * invalid. + */ +double +mpeg1_2_get_fps(int idx) { + static const int fps[8] = {0, 24, 25, 0, 30, 50, 0, 60}; + + if ((idx < 1) || (idx > 8)) + return -1.0; + switch (idx) { + case MPEGVIDEO_FPS_23_976: + return (double)24000.0 / 1001.0; + case MPEGVIDEO_FPS_29_97: + return (double)30000.0 / 1001.0; + case MPEGVIDEO_FPS_59_94: + return (double)60000.0 / 1001.0; + default: + return fps[idx - 1]; + } +} diff --git a/src/common/mpeg4_common.h b/src/common/mpeg4_common.h index 1952ea735..6d0765733 100644 --- a/src/common/mpeg4_common.h +++ b/src/common/mpeg4_common.h @@ -8,7 +8,7 @@ * * $Id$ * - * MPEG4 video helper functions + * MPEG1, 2 and 4 video helper functions * * Written by Moritz Bunkus . */ @@ -16,7 +16,27 @@ #ifndef __MPEG4_COMMON_H #define __MPEG4_COMMON_H -#define VOP_START_CODE 0x000001b6 +/** Start code for a MPEG4 (?) video object plain */ +#define MPEGVIDEO_OBJECT_PLAIN_START_CODE 0x000001b6 +/** Start code for a MPEG-1 and -2 sequence header */ +#define MPEGVIDEO_SEQUENCE_START_CODE 0x000001b3 + +/** MPEG-1/-2 frame rate: 24000/1001 frames per second */ +#define MPEGVIDEO_FPS_23_976 0x01 +/** MPEG-1/-2 frame rate: 24 frames per second */ +#define MPEGVIDEO_FPS_24 0x02 +/** MPEG-1/-2 frame rate: 25 frames per second */ +#define MPEGVIDEO_FPS_25 0x03 +/** MPEG-1/-2 frame rate: 30000/1001 frames per second */ +#define MPEGVIDEO_FPS_29_97 0x04 +/** MPEG-1/-2 frame rate: 30 frames per second */ +#define MPEGVIDEO_FPS_30 0x05 +/** MPEG-1/-2 frame rate: 50 frames per second */ +#define MPEGVIDEO_FPS_50 0x06 +/** MPEG-1/-2 frame rate: 60000/1001 frames per second */ +#define MPEGVIDEO_FPS_59_94 0x07 +/** MPEG-1/-2 frame rate: 60 frames per second */ +#define MPEGVIDEO_FPS_60 0x08 typedef struct { unsigned char *data; @@ -28,7 +48,11 @@ typedef struct { bool MTX_DLL_API mpeg4_extract_par(const unsigned char *buffer, int size, uint32_t &par_num, uint32_t &par_den); -void MTX_DLL_API mpeg4_find_frame_types(unsigned char *buf, int size, +void MTX_DLL_API mpeg4_find_frame_types(const unsigned char *buf, int size, vector &frames); +int MTX_DLL_API mpeg1_2_extract_fps_idx(const unsigned char *buffer, + int size); +double MTX_DLL_API mpeg1_2_get_fps(int idx); + #endif /* __MPEG4_COMMON_H */ diff --git a/src/input/r_qtmp4.cpp b/src/input/r_qtmp4.cpp index 73e5c2329..f5e516a48 100644 --- a/src/input/r_qtmp4.cpp +++ b/src/input/r_qtmp4.cpp @@ -1166,7 +1166,7 @@ qtmp4_reader_c::create_packetizer(int64_t tid) { codec_id = mxsprintf("V_MPEG%c", dmx->fourcc[3]); dmx->ptzr = add_packetizer(new video_packetizer_c(this, codec_id.c_str(), - 0.0, dmx->v_width, + -1.0, dmx->v_width, dmx->v_height, false, ti)); } else { diff --git a/src/output/p_video.cpp b/src/output/p_video.cpp index 5ebd7b0fd..9b28c5110 100644 --- a/src/output/p_video.cpp +++ b/src/output/p_video.cpp @@ -69,6 +69,8 @@ video_packetizer_c::video_packetizer_c(generic_reader_c *nreader, !strncmp(ncodec_id, MKV_V_MPEG4_SP, strlen(MKV_V_MPEG4_SP) - 2)) is_mpeg4 = true; + is_mpeg1_2 = !strcmp(ncodec_id, "V_MPEG1") || !strcmp(ncodec_id, "V_MPEG2"); + if (ncodec_id != NULL) set_codec_id(ncodec_id); else if (is_mpeg4 && hack_engaged(ENGAGE_NATIVE_MPEG4)) { @@ -98,7 +100,7 @@ video_packetizer_c::set_headers() { set_track_min_cache(2); else set_track_min_cache(1); - if (fps != 0.0) + if (fps > 0.0) set_track_default_duration((int64_t)(1000000000.0 / fps)); set_video_pixel_width(width); @@ -129,8 +131,12 @@ video_packetizer_c::process(memory_c &mem, debug_enter("video_packetizer_c::process"); + if (is_mpeg1_2 && (fps < 0.0)) + extract_mpeg1_2_fps(mem.data, mem.size); + if (hack_engaged(ENGAGE_NATIVE_MPEG4) && is_mpeg4) mpeg4_find_frame_types(mem.data, mem.size, frames); + if (hack_engaged(ENGAGE_NATIVE_MPEG4) && is_mpeg4 && (fps != 0.0)) { for (i = 0; i < frames.size(); i++) { if ((frames[i].type == 'I') || @@ -192,7 +198,7 @@ video_packetizer_c::process(memory_c &mem, } if (is_mpeg4 && !aspect_ratio_extracted) - extract_mpeg4_aspect_ratio(mem); + extract_mpeg4_aspect_ratio(mem.data, mem.size); if (old_timecode == -1) timecode = (int64_t)(1000000000.0 * frames_output / fps) + duration_shift; @@ -239,14 +245,15 @@ video_packetizer_c::flush() { } void -video_packetizer_c::extract_mpeg4_aspect_ratio(memory_c &mem) { +video_packetizer_c::extract_mpeg4_aspect_ratio(const unsigned char *buffer, + int size) { uint32_t num, den; aspect_ratio_extracted = true; if (ti->aspect_ratio_given || ti->display_dimensions_given) return; - if (mpeg4_extract_par(mem.data, mem.size, num, den)) { + if (mpeg4_extract_par(buffer, size, num, den)) { ti->aspect_ratio_given = true; ti->aspect_ratio = (float)hvideo_pixel_width / (float)hvideo_pixel_height * (float)num / (float)den; @@ -259,6 +266,22 @@ video_packetizer_c::extract_mpeg4_aspect_ratio(memory_c &mem) { } } +void +video_packetizer_c::extract_mpeg1_2_fps(const unsigned char *buffer, + int size) { + int idx; + + idx = mpeg1_2_extract_fps_idx(buffer, size); + if (idx >= 0) { + fps = mpeg1_2_get_fps(idx); + if (fps > 0) { + set_track_default_duration((int64_t)(1000000000.0 / fps)); + rerender_track_headers(); + } else + fps = 0.0; + } +} + void video_packetizer_c::flush_frames(char next_frame, bool flush_all) { diff --git a/src/output/p_video.h b/src/output/p_video.h index 5846253fa..92486f0a2 100644 --- a/src/output/p_video.h +++ b/src/output/p_video.h @@ -31,7 +31,7 @@ private: double fps; int width, height, bpp, frames_output; int64_t ref_timecode, duration_shift; - bool avi_compat_mode, bframes, pass_through, is_mpeg4; + bool avi_compat_mode, bframes, pass_through, is_mpeg4, is_mpeg1_2; bool aspect_ratio_extracted; vector queued_frames; video_frame_t bref_frame, fref_frame; @@ -58,7 +58,9 @@ public: protected: virtual void flush_frames(char next_frame = '?', bool flush_all = false); - virtual void extract_mpeg4_aspect_ratio(memory_c &mem); + virtual void extract_mpeg4_aspect_ratio(const unsigned char *buffer, + int size); + virtual void extract_mpeg1_2_fps(const unsigned char *buffer, int size); }; #endif // __P_VIDEO_H