blob: 53eeb9f69f210af0a7e5bd76ac01915e69e566b8 [file] [log] [blame]
// 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