1 /** MPEG video helper functions (MPEG 1, 2 and 4)
2 
3    mkvmerge -- utility for splicing together matroska files
4    from component media subtypes
5 
6    Distributed under the GPL v2
7    see the file COPYING for details
8    or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 
10    \file
11 
12    \author Written by Moritz Bunkus <moritz@bunkus.org>.
13 */
14 
15 #include "common/common_pch.h"
16 
17 #include "common/debugging.h"
18 #include "common/endian.h"
19 #include "common/mpeg1_2.h"
20 
21 namespace mtx::mpeg1_2 {
22 
23 namespace {
24 debugging_option_c s_debug{"mpeg1_2"};
25 }
26 
27 /** \brief Extract the FPS from a MPEG video sequence header
28 
29    This function looks for a MPEG sequence header in a buffer containing
30    a MPEG1 or MPEG2 video frame. If such a header is found its
31    FPS index is extracted and returned. This index can be mapped to the
32    actual number of frames per second with the function
33    ::mpeg_video_get_fps
34 
35    \param buffer The buffer to search for the header.
36    \param buffer_size The buffer size.
37 
38    \return The index or \c -1 if no MPEG sequence header was found or
39      if the buffer was too small.
40 */
41 int
extract_fps_idx(unsigned char const * buffer,int buffer_size)42 extract_fps_idx(unsigned char const *buffer,
43                 int buffer_size) {
44   mxdebug_if(s_debug, fmt::format("mpeg_video_fps: start search in {0} bytes\n", buffer_size));
45   if (buffer_size < 8) {
46     mxdebug_if(s_debug, "mpeg_video_fps: sequence header too small\n");
47     return -1;
48   }
49   auto marker = get_uint32_be(buffer);
50   int idx     = 4;
51   while ((idx < buffer_size) && (marker != SEQUENCE_HEADER_START_CODE)) {
52     marker <<= 8;
53     marker |= buffer[idx];
54     idx++;
55   }
56 
57   if ((idx + 3) >= buffer_size) {
58     mxdebug_if(s_debug, "mpeg_video_fps: no full sequence header start code found\n");
59     return -1;
60   }
61 
62   mxdebug_if(s_debug, fmt::format("mpeg_video_fps: found sequence header start code at {0}\n", idx - 4));
63 
64   return buffer[idx + 3] & 0x0f;
65 }
66 
67 /** \brief Extract the aspect ratio from a MPEG video sequence header
68 
69    This function looks for a MPEG sequence header in a buffer containing
70    a MPEG1 or MPEG2 video frame. If such a header is found its
71    aspect ratio is extracted and returned.
72 
73    \param buffer The buffer to search for the header.
74    \param buffer_size The buffer size.
75 
76    \return \c true if a MPEG sequence header was found and \c false otherwise.
77 */
78 std::optional<mtx_mp_rational_t>
extract_aspect_ratio(unsigned char const * buffer,int buffer_size)79 extract_aspect_ratio(unsigned char const *buffer,
80                      int buffer_size) {
81   uint32_t marker;
82   int idx;
83 
84   mxdebug_if(s_debug, fmt::format("mpeg_video_ar: start search in {0} bytes\n", buffer_size));
85   if (buffer_size < 8) {
86     mxdebug_if(s_debug, "mpeg_video_ar: sequence header too small\n");
87     return {};
88   }
89   marker = get_uint32_be(buffer);
90   idx = 4;
91   while ((idx < buffer_size) && (marker != SEQUENCE_HEADER_START_CODE)) {
92     marker <<= 8;
93     marker |= buffer[idx];
94     idx++;
95   }
96   if (idx >= buffer_size) {
97     mxdebug_if(s_debug, "mpeg_video_ar: no sequence header start code found\n");
98     return {};
99   }
100 
101   mxdebug_if(s_debug, fmt::format("mpeg_video_ar: found sequence header start code at {0}\n", idx - 4));
102   idx += 3;                     // width and height
103   if (idx >= buffer_size) {
104     mxdebug_if(s_debug, "mpeg_video_ar: sequence header too small\n");
105     return {};
106   }
107 
108   switch (buffer[idx] & 0xf0) {
109     case AR_1_1:  return mtx::rational(1, 1);
110     case AR_4_3:  return mtx::rational(4, 3);
111     case AR_16_9: return mtx::rational(16, 9);
112     case AR_2_21: return mtx::rational(221, 100);
113   }
114 
115   return {};
116 }
117 
118 /** \brief Get the number of frames per second
119 
120    Converts the index returned by ::mpeg_video_extract_fps_idx to a number.
121 
122    \param idx The index as to convert.
123 
124    \return The number of frames per second or \c -1.0 if the index was
125      invalid.
126 */
127 double
get_fps(int idx)128 get_fps(int idx) {
129   static const int s_fps[8] = {0, 24, 25, 0, 30, 50, 0, 60};
130 
131   return ((idx < 1) || (idx > 8)) ?             -1.0
132       : FPS_23_976 == idx         ? 24000.0 / 1001.0
133       : FPS_29_97  == idx         ? 30000.0 / 1001.0
134       : FPS_59_94  == idx         ? 60000.0 / 1001.0
135       :                             s_fps[idx - 1];
136 }
137 
138 bool
is_fourcc(uint32_t fourcc)139 is_fourcc(uint32_t fourcc) {
140   return (FOURCC_MPEG1 == fourcc) || (FOURCC_MPEG2 == fourcc);
141 }
142 
143 } // namespace mtx::mpeg1_2
144