| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // #define LOG_NDEBUG 0 |
| #define LOG_TAG "EncodedDataHelper" |
| |
| #include "arc/codec-test/encoded_data_helper.h" |
| |
| #include <assert.h> |
| #include <string.h> |
| |
| #include <utility> |
| |
| #include <utils/Log.h> |
| |
| namespace android { |
| |
| namespace { |
| |
| bool IsAnnexb3ByteStartCode(const std::string& data, size_t pos) { |
| // The Annex-B 3-byte start code "\0\0\1" will be prefixed by NALUs per AU |
| // except for the first one. |
| return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 1; |
| } |
| |
| bool IsAnnexb4ByteStartCode(const std::string& data, size_t pos) { |
| // The Annex-B 4-byte start code "\0\0\0\1" will be prefixed by first NALU per |
| // AU. |
| return data[pos] == 0 && data[pos + 1] == 0 && data[pos + 2] == 0 && |
| data[pos + 3] == 1; |
| } |
| |
| // Get the next position of NALU header byte in |data| from |next_header_pos|, |
| // and update to |next_header_pos|. Return true if there is one; false |
| // otherwise. |
| // Note: this function should be used within an AU. |
| bool GetPosForNextNALUHeader(const std::string& data, size_t* next_header_pos) { |
| size_t pos = *next_header_pos; |
| |
| // Annex-B 4-byte could be also found by IsAnnexb3ByteStartCode(). |
| while (pos + 3 <= data.size() && !IsAnnexb3ByteStartCode(data, pos)) { |
| ++pos; |
| } |
| if (pos + 3 >= data.size()) |
| return false; // No more NALUs |
| |
| // NALU header is the first byte after Annex-B start code. |
| *next_header_pos = pos + 3; |
| return true; |
| } |
| |
| // For H264, return data bytes of next AU fragment in |data| from |next_pos|, |
| // and update the position to |next_pos|. |
| std::string GetBytesForNextAU(const std::string& data, size_t* next_pos) { |
| // Helpful description: |
| // https://en.wikipedia.org/wiki/Network_Abstraction_Layer |
| size_t start_pos = *next_pos; |
| size_t pos = start_pos; |
| if (pos + 4 > data.size()) { |
| ALOGE("Invalid AU: Start code is less than 4 bytes.\n"); |
| *next_pos = data.size(); |
| return std::string(); |
| } |
| |
| assert(IsAnnexb4ByteStartCode(data, pos)); |
| |
| pos += 4; |
| // The first 4 bytes must be Annex-B 4-byte start code for an AU. |
| while (pos + 4 <= data.size() && !IsAnnexb4ByteStartCode(data, pos)) { |
| ++pos; |
| } |
| if (pos + 3 >= data.size()) |
| pos = data.size(); |
| |
| // Update next_pos. |
| *next_pos = pos; |
| return data.substr(start_pos, pos - start_pos); |
| } |
| |
| // For VP8/9, return data bytes of next frame in |data| from |next_pos|, and |
| // update the position to |next_pos|. |
| std::string GetBytesForNextFrame(const std::string& data, size_t* next_pos) { |
| // Helpful description: http://wiki.multimedia.cx/index.php?title=IVF |
| size_t pos = *next_pos; |
| std::string bytes; |
| if (pos == 0) |
| pos = 32; // Skip IVF header. |
| |
| const uint32_t frame_size = *reinterpret_cast<const uint32_t*>(&data[pos]); |
| pos += 12; // Skip frame header. |
| |
| // Update next_pos. |
| *next_pos = pos + frame_size; |
| return data.substr(pos, frame_size); |
| } |
| |
| } // namespace |
| |
| EncodedDataHelper::EncodedDataHelper(const std::string& file_path, |
| VideoCodecType type) |
| : type_(type) { |
| InputFileStream input(file_path); |
| if (!input.IsValid()) { |
| ALOGE("Failed to open file: %s", file_path.c_str()); |
| return; |
| } |
| |
| int file_size = input.GetLength(); |
| if (file_size <= 0) { |
| ALOGE("Stream byte size (=%d) is invalid", file_size); |
| return; |
| } |
| input.Rewind(); |
| |
| char* read_bytes = new char[file_size]; |
| if (input.Read(read_bytes, file_size) != file_size) { |
| ALOGE("Failed to read input stream from file to buffer."); |
| return; |
| } |
| |
| // Note: must assign |file_size| here otherwise the constuctor will terminate |
| // copting at the first '\0' in |read_bytes|. |
| std::string data(read_bytes, file_size); |
| delete[] read_bytes; |
| |
| SliceToFragments(data); |
| } |
| |
| EncodedDataHelper::~EncodedDataHelper() {} |
| |
| const EncodedDataHelper::Fragment* const EncodedDataHelper::GetNextFragment() { |
| if (ReachEndOfStream()) |
| return nullptr; |
| return next_fragment_iter_++->get(); |
| } |
| |
| bool EncodedDataHelper::AtHeadOfStream() const { |
| return next_fragment_iter_ == fragments_.begin(); |
| } |
| |
| bool EncodedDataHelper::ReachEndOfStream() const { |
| return next_fragment_iter_ == fragments_.end(); |
| } |
| |
| void EncodedDataHelper::SliceToFragments(const std::string& data) { |
| size_t next_pos = 0; |
| bool seen_csd = false; |
| while (next_pos < data.size()) { |
| std::unique_ptr<Fragment> fragment(new Fragment()); |
| switch (type_) { |
| case VideoCodecType::H264: |
| fragment->data = GetBytesForNextAU(data, &next_pos); |
| if (!ParseAUFragmentType(fragment.get())) |
| continue; |
| if (!seen_csd && !fragment->csd_flag) |
| // Skip all AUs beforehand until we get SPS NALU. |
| continue; |
| seen_csd = true; |
| break; |
| case VideoCodecType::VP8: |
| case VideoCodecType::VP9: |
| fragment->data = GetBytesForNextFrame(data, &next_pos); |
| break; |
| default: |
| ALOGE("Unknown video codec type."); |
| return; |
| } |
| fragments_.push_back(std::move(fragment)); |
| } |
| |
| ALOGD("Total %zu fragments in interest from input stream.", |
| NumValidFragments()); |
| next_fragment_iter_ = fragments_.begin(); |
| } |
| |
| bool EncodedDataHelper::ParseAUFragmentType(Fragment* fragment) { |
| size_t next_header_pos = 0; |
| while (GetPosForNextNALUHeader(fragment->data, &next_header_pos)) { |
| // Read the NALU header (first byte) which contains unit type. |
| uint8_t nalu_header = static_cast<uint8_t>(fragment->data[next_header_pos]); |
| |
| // Check forbidden_zero_bit (MSB of NALU header) is 0; |
| if (nalu_header & 0x80) { |
| ALOGE("NALU header forbidden_zero_bit is 1."); |
| return false; |
| } |
| |
| // Check NALU type ([3:7], 5-bit). |
| uint8_t nalu_type = nalu_header & 0x1f; |
| switch (nalu_type) { |
| case NON_IDR_SLICE: |
| case IDR_SLICE: |
| // If AU contains both CSD and VCL NALUs (e.g. PPS + IDR_SLICE), don't |
| // raise csd_flag, treat this fragment as VCL one. |
| fragment->csd_flag = false; |
| return true; // fragment in interest as VCL. |
| case SPS: |
| case PPS: |
| fragment->csd_flag = true; |
| // Continue on finding the subsequent NALUs, it may have VCL data. |
| break; |
| default: |
| // Skip uninterested NALU type. |
| break; |
| } |
| } |
| return fragment->csd_flag; // fragment in interest as CSD. |
| } |
| |
| } // namespace android |