YUV-VIEWER之YUV解析器

YUV是视频原始数据存储格式,如何将文件中的YUV解析出来呢?

一、YUV概要

        YUV中的Y表示图像的亮度,即灰度值;U和V表示图像的色度,即图像的颜色。一帧YUV数据只提取Y分量,仍然可以完整的显示这一帧图像,但是黑白色。

        YUV存储格式为两种:

  1. planar : 平面格式,即先存储Y分量,再U分量,最后V分量
  2. packed :打包格式,即Y,U,V分量交叉存储

        按照UV分量比例不同,YUV存储方式分为:

  1. YUV444 :Y,U,V分量存储比例相同
  2. YUV422 :Y分量存储比例是U,V分量的1倍;U,V分量比例相同
  3. YUV420 :Y分量存储比例是U,V分量和的1倍;U,V分量比例相同

  YUV不同存储方式数据大小计算(图像分辨率:width * height):

  1. YUV444 :width * height + width * height + width * height = width * height * 3
  2. YUV422 :width * height + width * height  / 2 + widht * height / 2 = widht * height * 2
  3. YUV420 :widht * height + width * height / 4 + width * height / 4 = width * height * 3 / 2

  在实际编程中,需要根据YUV事件存储格式和存储方式动态提取或存储数据。

二、YUV解析器

  YUV数据结构

 1 namespace yuv{
 2     
 3     struct YUV
 4     {
 5         std::unique_ptr<uint8_t[]> Y;      // Y分量
 6         size_t YSize;
 7         std::unique_ptr<uint8_t[]> U;     // U分量
 8         size_t USize;
 9         std::unique_ptr<uint8_t[]> V;     // V分量
10         size_t VSize;
11     };
12 }

  YUV帧数据结构

1 namespace yuv{
2     struct YUVFrame
3     {
4         Resolution resolution;     // 分辨率
5         YUV        yuv;            // YUV分量数据 
6         size_t     size;           // 帧数据大小
7     };
8 }

  YUV解析器

  1 namespace yuv{
  2     template <typename ParserType>
  3     class YUVParser
  4     {
  5     public:
  6         using Frames = std::vector<YUVFrame>;
  7         constexpr YUVParser(const std::string& file, const Resolution& resolution) noexcept
  8             : _file(file)
  9             , _iFileStream(file, std::ios::binary)
 10             , _ext(Extension(file))
 11             , _resolution(resolution)
 12         {
 13         }
 14 
 15         virtual ~YUVParser() {}
 16 
 17         virtual bool Parse()
 18         {
 19             if (!_iFileStream.is_open()) {
 20                 return false;
 21             }
 22 
 23             const auto& resolution = GetResolution();
 24             const auto kFarmeSize = GetPerFrameSize();
 25             const auto frameCounts = CountFrames(_iFileStream, kFarmeSize);
 26             if (frameCounts == 0) {
 27                 return false;
 28             }
 29 
 30             _frames.reserve(frameCounts);
 31             auto buffer = std::make_unique<uint8_t[]>(kFarmeSize);
 32             while (true) {
 33                 std::memset(buffer.get(), 0x00, kFarmeSize);
 34                 _iFileStream.read(reinterpret_cast<char*>(buffer.get()), kFarmeSize);
 35                 auto readSize = _iFileStream.gcount();
 36                 if (readSize == 0) {
 37                     break;
 38                 }
 39                 if (readSize != kFarmeSize) {
 40                     continue;
 41                 }
 42 
 43                 auto frame = BuildYUVFrame(buffer.get());
 44                 _frames.push_back(std::move(frame));
 45             }
 46 
 47             return _frames.size();
 48         }
 49 
 50         bool DuplicateToFile(const std::string& duplicateFile) const
 51         {
 52             const auto& parser = static_cast<const ParserType&>(*this);
 53             return parser.DuplicateToFileImpl(duplicateFile);
 54         }
 55 
 56         bool DumpYToFile(const std::string& yFile) const
 57         {
 58             return DumpYToFileImpl(yFile);
 59         }
 60 
 61         inline const std::string& GetFilePath() const { return _file; }
 62         inline const std::string& GetExtension() const { return _ext; }
 63         inline const Resolution& GetResolution() const { return _resolution; }
 64         inline const Frames& GetFrames() const { return _frames; }
 65         inline const size_t GetFrameCounts() const { return _frames.size(); }
 66     protected:
 67         static constexpr auto Extension(const std::string& file)
 68         {
 69             using namespace std::filesystem;
 70             using namespace std::string_literals;
 71 
 72             if (u8path(file).has_extension()) {
 73                 return u8path(file).extension().u8string();
 74             }
 75             else {
 76                 return ".yuv"s;
 77             }
 78         }
 79         
 80         static size_t CountFrames(std::ifstream& iFileStream, size_t frameSize)
 81         {
 82             SeekBeg(iFileStream);
 83             auto begPos = iFileStream.tellg();
 84             iFileStream.seekg(0, std::ios_base::end);
 85             auto endPos = iFileStream.tellg();
 86             SeekBeg(iFileStream);
 87 
 88             return static_cast<size_t>(endPos - begPos) / frameSize;
 89         }
 90 
 91         static void SeekBeg(std::ifstream& iFileStream)
 92         {
 93             iFileStream.seekg(0, std::ios_base::beg);
 94         }
 95         
 96         virtual YUVFrame BuildYUVFrame(const uint8_t* buf) const = 0;
 97         virtual size_t GetPerFrameSize() const = 0;
 98 
 99         virtual bool DumpYToFileImpl(const std::string& yFile) const
100         {
101             std::ofstream oYFileStream(yFile, std::ios::binary);
102             if (oYFileStream.is_open() && GetFrameCounts()) {
103                 for (const auto& frame : GetFrames()) {
104                     oYFileStream.write(reinterpret_cast<char*>(frame.yuv.Y.get()), frame.yuv.YSize);
105                 }
106                 return true;
107             }
108             else {
109                 return false;
110             }
111         }
112 
113         virtual YUVFrame CreateYUVFrame() const
114         {
115             YUVFrame yuvFrame;
116             yuvFrame.resolution = GetResolution();
117             yuvFrame.size = GetPerFrameSize();
118             yuvFrame.yuv.YSize = GetYSize();
119             yuvFrame.yuv.Y = std::make_unique<uint8_t[]>(yuvFrame.yuv.YSize);
120             yuvFrame.yuv.USize = GetUSize();
121             yuvFrame.yuv.U = std::make_unique<uint8_t[]>(yuvFrame.yuv.USize);
122             yuvFrame.yuv.VSize = GetVSize();
123             yuvFrame.yuv.V = std::make_unique<uint8_t[]>(yuvFrame.yuv.VSize);
124 
125             return yuvFrame;
126         }
127 
128         virtual void FillYUVFrame(const uint8_t* buf, YUVFrame& frame) const = 0;
129         virtual size_t GetYSize() const
130         {
131             return _resolution.width * _resolution.height;
132         }
133         virtual size_t GetUSize() const = 0;
134         virtual size_t GetVSize() const
135         {
136             return GetUSize();
137         }
138     protected:
139         const std::string   _file;
140         const std::string   _ext;
141         const Resolution    _resolution;
142         std::ifstream       _iFileStream;
143         Frames              _frames;
144     };  
145 }

  YUV444P解析器(planar)

 1 namesapce yuv{
 2     // YUV444P Foramt
 3     // example : 4 * 4
 4     //  -------------------------------
 5     // |  Y00  |  Y01  |  Y02  |  Y03  |
 6     //  -------------------------------
 7     // |  Y10  |  Y11  |  Y12  |  Y13  |
 8     //  -------------------------------
 9     // |  Y20  |  Y21  |  Y22  |  Y23  |
10     //  -------------------------------
11     // |  Y30  |  Y31  |  Y32  |  Y33  |
12     //  -------------------------------
13     // |  U40  |  U41  |  U42  |  U43  |
14     //  -------------------------------
15     // |  U50  |  U51  |  U52  |  U53  |
16     //  -------------------------------
17     // |  U60  |  U61  |  U62  |  U63  |
18     //  -------------------------------
19     // |  U70  |  U71  |  U72  |  U73  |
20     //  -------------------------------
21     // |  V80  |  V81  |  V82  |  V83  |
22     //  -------------------------------
23     // |  V90  |  V91  |  V92  |  V93  |
24     //  -------------------------------
25     // |  V100 |  V101 |  V102 |  V103 |
26     //  -------------------------------
27     // |  V110 |  V111 |  V112 |  V113 |
28     //  -------------------------------  
29 
30     class YUVParser444P
31         : public YUVParser420P 
32     {
33     public:
34         YUVParser444P(const std::string& file, const Resolution& resolution)
35             : YUVParser420P(file, resolution)
36         {}
37 
38     private:
39         size_t GetPerFrameSize() const override
40         {
41             return _resolution.width * _resolution.height * 3;
42         }
43 
44         size_t GetUSize() const override
45         {
46             return GetYSize();
47         }
48     };
49 }    

  YUV422P解析器(planar)

 1 namespace yuv{
 2     // YUV422P Foramt
 3     // example : 4 * 4
 4     //  -------------------------------
 5     // |  Y00  |  Y01  |  Y02  |  Y03  |
 6     //  -------------------------------
 7     // |  Y10  |  Y11  |  Y12  |  Y13  |
 8     //  -------------------------------
 9     // |  Y20  |  Y21  |  Y22  |  Y23  |
10     //  -------------------------------
11     // |  Y30  |  Y31  |  Y32  |  Y33  |
12     //  -------------------------------
13     // |  U40  |  U41  |  U42  |  U43  |
14     //  -------------------------------
15     // |  U50  |  U51  |  U52  |  U53  |
16     //  -------------------------------
17     // |  V60  |  V61  |  V62  |  V63  |
18     //  -------------------------------
19     // |  V70  |  V71  |  V72  |  V73  |
20     //  -------------------------------
21     class YUVParser422P
22         : public YUVParser420P
23     {
24     public:
25         YUVParser422P(const std::string& file, const Resolution& resolution)
26             : YUVParser420P(file, resolution)
27         {}
28     private:
29         size_t GetPerFrameSize() const override
30         {
31             return _resolution.width * _resolution.height * 2;
32         }
33 
34         size_t GetUSize() const override
35         {
36             return GetYSize() / 2;
37         }
38     };
39 }

  YUV420P解析器(planar)

 1 namespace yuv{
 2     // YUV420P Format
 3     // Example : 4 * 4
 4     //  -------------------------------
 5     // |  Y00  |  Y01  |  Y02  |  Y03  |
 6     //  -------------------------------
 7     // |  Y10  |  Y11  |  Y12  |  Y13  |
 8     //  -------------------------------
 9     // |  Y20  |  Y21  |  Y22  |  Y23  |
10     //  -------------------------------
11     // |  Y30  |  Y31  |  Y32  |  Y33  |
12     //  -------------------------------
13     // |  U40  |  U41  |  U42  |  U43  |
14     //  -------------------------------
15     // |  V50  |  V51  |  V52  |  V53  |
16     //  -------------------------------
17     template <typename ParserType> class YUVParser;
18     class YUVParser420P
19         : public YUVParser<YUVParser420P>
20     {
21         friend class YUVParser<YUVParser420P>;
22     public:
23         YUVParser420P(const std::string& file, const Resolution& resolution)
24             : YUVParser(file, resolution)
25         {}
26     private:
27         bool DuplicateToFileImpl(const std::string& duplicateFile) const
28         {
29             std::ofstream oDuplicateFileStream(duplicateFile, std::ios::binary);
30             if (oDuplicateFileStream.is_open() && GetFrameCounts()) {
31                 for (const auto& frame : GetFrames()) {
32                     oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.Y.get()), frame.yuv.YSize);
33                     oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.U.get()), frame.yuv.USize);
34                     oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.V.get()), frame.yuv.VSize);
35                 }
36                 return true;
37             }
38             else {
39                 return false;
40             }
41         }
42 
43         YUVFrame BuildYUVFrame(const uint8_t* buf) const override
44         {
45             auto frame = CreateYUVFrame();
46             FillYUVFrame(buf, frame);
47             return frame;
48         }
49         
50         size_t GetPerFrameSize() const override
51         {
52             return _resolution.width * _resolution.height * 3 / 2;
53         }
54 
55         void FillYUVFrame(const uint8_t* buf, YUVFrame& frame) const override
56         {
57             std::memcpy(frame.yuv.Y.get(),
58                 buf,
59                 frame.yuv.YSize);
60             std::memcpy(frame.yuv.U.get(),
61                 buf
62                 + frame.yuv.YSize,
63                 frame.yuv.USize);
64             std::memcpy(frame.yuv.V.get(),
65                 buf
66                 + frame.yuv.YSize
67                 + frame.yuv.USize,
68                 frame.yuv.VSize);
69         }
70 
71         size_t GetUSize() const override
72         {
73             return GetYSize() / 4;
74         }
75     };
76 }

  YUV420SP解析器(packed)

 1 namespace yuv{
 2     // YUV420SP Foramt
 3     //  -------------------------------
 4     // |  Y00  |  Y01  |  Y02  |  Y03  |
 5     //  -------------------------------
 6     // |  Y10  |  Y11  |  Y12  |  Y13  |
 7     //  -------------------------------
 8     // |  Y20  |  Y21  |  Y22  |  Y23  |
 9     //  -------------------------------
10     // |  Y30  |  Y31  |  Y32  |  Y33  |
11     //  -------------------------------
12     // |  U40  |  V41  |  U42  |  V43  |
13     //  -------------------------------
14     // |  U50  |  V51  |  U52  |  V53  |
15     //  -------------------------------1
16     class YUVParser420SP
17         : public YUVParser420P 
18     {
19     public:
20         YUVParser420SP(const std::string& file, const Resolution& resolution)
21             : YUVParser420P(file, resolution)
22         {}
23 
24     private:
25         bool DuplicateToFileImpl(const std::string& duplicateFile) const
26         {
27             std::ofstream oDuplicateFileStream(duplicateFile, std::ios::binary);
28             if (oDuplicateFileStream.is_open() && GetFrameCounts()) {
29                 for (const auto& frame : GetFrames()) {
30                     oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.Y.get()), frame.yuv.YSize);
31                     for (size_t index = 0; index < frame.yuv.USize; ++index) {
32                         oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.U.get()), 1);
33                         oDuplicateFileStream.write(reinterpret_cast<char*>(frame.yuv.V.get()), 1);
34                     }
35                 }
36                 return true;
37             }
38             else {
39                 return false;
40             }
41         }
42 
43         void FillYUVFrame(const uint8_t* buf, YUVFrame& frame) const override
44         {
45             std::memcpy(frame.yuv.Y.get(),
46                 buf,
47                 frame.yuv.YSize);
48             const auto& uvBuf = buf + frame.yuv.YSize;
49             const auto& uvSize = frame.yuv.USize + frame.yuv.VSize;
50             for (size_t index = frame.yuv.YSize, uIndex = 0, vIndex = 0; index < uvSize;) {
51                 std::memcpy(frame.yuv.U.get() + uIndex++,
52                     buf + index++,
53                     1);
54 
55                 std::memcpy(frame.yuv.V.get() + vIndex++,
56                     buf + index++,
57                     1);
58             }
59         }
60     };
61 }

  ... ...

三、测试样例

 1 int main()
 2 {
 3     using namespace yuv;
 4     constexpr auto kYUVFile = u8R"(.\res\file.xxx)";
 5     const Resolution resolution{ 1920,1080 };
 6     // 444P
 7     YUVParser444P parser(kYUVFile, resolution);
 8     // 422P
 9     YUVParser422P parser(kYUVFile, resolution);
10     // 420P
11     YUVParser420P parser(kYUVFile, resolution);
12     // 420SP
13     YUVParser420SP parser(kYUVFile, resolution);
14 
15     parser.Parse();
16     std::cerr << "frames = > " << parser.GetFrameCounts() << "\n";
17     
18     const auto& ext = parser.GetExtension();
19     const auto filename = "y_file" + ext;
20     const auto duplicateFilename = "duplicate" + ext;
21     parser.DuplicateToFile(".//" + duplicateFilename);
22     parser.DumpYToFile(".//" + filename);
23 
24     return 0;
25 }

参考文献:

  • http://www.chiark.greenend.org.uk/doc/linux-doc-3.16/html/media_api/yuv-formats.html
posted @ 2021-12-10 19:20  blackstar666  阅读(776)  评论(0编辑  收藏  举报