YUV是视频原始数据存储格式,如何将文件中的YUV解析出来呢?
一、YUV概要
YUV中的Y表示图像的亮度,即灰度值;U和V表示图像的色度,即图像的颜色。一帧YUV数据只提取Y分量,仍然可以完整的显示这一帧图像,但是黑白色。
YUV存储格式为两种:
- planar : 平面格式,即先存储Y分量,再U分量,最后V分量
- packed :打包格式,即Y,U,V分量交叉存储
按照UV分量比例不同,YUV存储方式分为:
- YUV444 :Y,U,V分量存储比例相同
- YUV422 :Y分量存储比例是U,V分量的1倍;U,V分量比例相同
- YUV420 :Y分量存储比例是U,V分量和的1倍;U,V分量比例相同
YUV不同存储方式数据大小计算(图像分辨率:width * height):
- YUV444 :width * height + width * height + width * height = width * height * 3
- YUV422 :width * height + width * height / 2 + widht * height / 2 = widht * height * 2
- 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