blob: e57ef50a19fa85afa0da039fe162ee1d5c0f7ca8 [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 "MediaCodecDecoder"
#include "arc/codec-test/mediacodec_decoder.h"
#include <assert.h>
#include <inttypes.h>
#include <utility>
#include <vector>
#include <media/NdkMediaFormat.h>
#include <utils/Log.h>
namespace android {
namespace {
// The timeout of AMediaCodec_dequeueOutputBuffer function calls.
constexpr int kTimeoutWaitForOutputUs = 500000; // 0.5 seconds
// The timeout of AMediaCodec_dequeueInputBuffer function calls.
constexpr int kTimeoutWaitForInputUs = 5000; // 5 milliseconds
// The maximal retry times of doDecode routine.
constexpr size_t kTimeoutMaxRetries = 20;
// The specified framerate for generating input timestamps.
constexpr int32_t kFrameRate = 25;
// Helper function to get possible decoder names from |type|.
std::vector<const char*> GetArcVideoDecoderNames(VideoCodecType type) {
switch (type) {
case VideoCodecType::H264:
return {"c2.vda.avc.decoder", "ARC.h264.decode"};
case VideoCodecType::VP8:
return {"c2.vda.vp8.decoder", "ARC.vp8.decode"};
case VideoCodecType::VP9:
return {"c2.vda.vp9.decoder", "ARC.vp9.decode"};
default: // unknown type
return {};
}
}
#if ANDROID_VERSION >= 0x0900
const uint32_t BUFFER_FLAG_CODEC_CONFIG = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
const char* FORMAT_KEY_SLICE_HEIGHT = AMEDIAFORMAT_KEY_SLICE_HEIGHT;
#else
// Define non-exported constants of MediaCodec NDK interface here for usage of
// Android Version < Pie.
const uint32_t BUFFER_FLAG_CODEC_CONFIG = 2;
const char* FORMAT_KEY_SLICE_HEIGHT = "slice-height";
#endif
} // namespace
// static
std::unique_ptr<MediaCodecDecoder> MediaCodecDecoder::Create(
const std::string& input_path,
VideoCodecProfile profile,
const Size& video_size) {
if (video_size.IsEmpty()) {
ALOGE("Size is not valid: %dx%d", video_size.width, video_size.height);
return nullptr;
}
VideoCodecType type = VideoCodecProfileToType(profile);
std::unique_ptr<EncodedDataHelper> encoded_data_helper(
new EncodedDataHelper(input_path, type));
if (!encoded_data_helper->IsValid()) {
ALOGE("EncodedDataHelper is not created for file: %s", input_path.c_str());
return nullptr;
}
AMediaCodec* codec = nullptr;
auto decoder_names = GetArcVideoDecoderNames(type);
for (const auto& decoder_name : decoder_names) {
codec = AMediaCodec_createCodecByName(decoder_name);
if (codec) {
ALOGD("Created mediacodec decoder by name: %s", decoder_name);
break;
}
}
if (!codec) {
ALOGE("Failed to create mediacodec decoder.");
return nullptr;
}
return std::unique_ptr<MediaCodecDecoder>(new MediaCodecDecoder(
codec, std::move(encoded_data_helper), type, video_size));
}
MediaCodecDecoder::MediaCodecDecoder(
AMediaCodec* codec,
std::unique_ptr<EncodedDataHelper> encoded_data_helper,
VideoCodecType type,
const Size& size)
: codec_(codec),
encoded_data_helper_(std::move(encoded_data_helper)),
type_(type),
input_visible_size_(size) {}
MediaCodecDecoder::~MediaCodecDecoder() {
if (codec_ != nullptr) {
AMediaCodec_delete(codec_);
}
}
void MediaCodecDecoder::SetOutputBufferReadyCb(const OutputBufferReadyCb& cb) {
output_buffer_ready_cb_ = cb;
}
void MediaCodecDecoder::SetOutputFormatChangedCb(
const OutputFormatChangedCb& cb) {
output_format_changed_cb_ = cb;
}
void MediaCodecDecoder::Rewind() {
encoded_data_helper_->Rewind();
input_fragment_index_ = 0;
}
bool MediaCodecDecoder::Configure() {
ALOGD("configure: mime=%s, width=%d, height=%d", GetMimeType(type_),
input_visible_size_.width, input_visible_size_.height);
AMediaFormat* format = AMediaFormat_new();
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, GetMimeType(type_));
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH,
input_visible_size_.width);
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT,
input_visible_size_.height);
media_status_t ret =
AMediaCodec_configure(codec_, format, nullptr /* surface */,
nullptr /* crtpto */, 0 /* flag */);
AMediaFormat_delete(format);
if (ret != AMEDIA_OK) {
ALOGE("Configure return error: %d", ret);
return false;
}
return true;
}
bool MediaCodecDecoder::Start() {
media_status_t ret = AMediaCodec_start(codec_);
if (ret != AMEDIA_OK) {
ALOGE("Start return error: %d", ret);
return false;
}
return true;
}
bool MediaCodecDecoder::Decode() {
while (!output_done_) {
size_t retries = 0;
bool success = false;
// It will keep retrying until one output buffer is dequeued successfully.
// On each retry we would like to enqueue input buffers as fast as possible.
// The retry loop will break as failure if maxmimum retries are reached or
// errors returned from enqueue input buffer or dequeue output buffer.
while (retries < kTimeoutMaxRetries && !success) {
if (!EnqueueInputBuffers())
return false;
switch (DequeueOutputBuffer()) {
case DequeueStatus::RETRY:
retries++;
break;
case DequeueStatus::SUCCESS:
success = true;
break;
case DequeueStatus::FAILURE:
return false;
}
}
if (retries >= kTimeoutMaxRetries) {
ALOGE("Decoder did not produce an output buffer after %zu retries",
kTimeoutMaxRetries);
}
if (!success)
return false;
}
return true;
}
bool MediaCodecDecoder::EnqueueInputBuffers() {
ssize_t index;
while (!input_done_) {
index = AMediaCodec_dequeueInputBuffer(codec_, kTimeoutWaitForInputUs);
if (index == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
return true; // no available input buffers, try next time
if (index < 0) {
ALOGE("Unknown error while dequeueInputBuffer: %zd", index);
return false;
}
if (encoded_data_helper_->ReachEndOfStream()) {
if (!FeedEOSInputBuffer(index))
return false;
input_done_ = true;
} else {
if (!FeedInputBuffer(index))
return false;
}
}
return true;
}
MediaCodecDecoder::DequeueStatus MediaCodecDecoder::DequeueOutputBuffer() {
AMediaCodecBufferInfo info;
ssize_t index =
AMediaCodec_dequeueOutputBuffer(codec_, &info, kTimeoutWaitForOutputUs);
switch (index) {
case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
ALOGV("Try again later is reported");
return DequeueStatus::RETRY;
case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
ALOGV("Output buffers changed");
return DequeueStatus::RETRY;
case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED:
ALOGV("Output format changed");
if (GetOutputFormat())
return DequeueStatus::SUCCESS;
else
return DequeueStatus::FAILURE;
default:
if (index < 0) {
ALOGE("Unknown error while dequeueOutputBuffer: %zd", index);
return DequeueStatus::FAILURE;
}
break;
}
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)
output_done_ = true;
if (!ReceiveOutputBuffer(index, info))
return DequeueStatus::FAILURE;
return DequeueStatus::SUCCESS;
}
bool MediaCodecDecoder::Stop() {
return AMediaCodec_stop(codec_) == AMEDIA_OK;
}
bool MediaCodecDecoder::FeedInputBuffer(size_t index) {
assert(!encoded_data_helper_->ReachEndOfStream());
size_t buf_size = 0;
uint8_t* buf = AMediaCodec_getInputBuffer(codec_, index, &buf_size);
if (!buf) {
ALOGE("Failed to getInputBuffer: index=%zu", index);
return false;
}
auto fragment = encoded_data_helper_->GetNextFragment();
assert(fragment);
if (buf_size < fragment->data.size()) {
ALOGE("Input buffer size is not enough: buf_size=%zu, data_size=%zu",
buf_size, fragment->data.size());
return false;
}
memcpy(reinterpret_cast<char*>(buf), fragment->data.data(),
fragment->data.size());
uint32_t input_flag = 0;
if (fragment->csd_flag)
input_flag |= BUFFER_FLAG_CODEC_CONFIG;
uint64_t timestamp_us = input_fragment_index_ * 1000000 / kFrameRate;
ALOGV("queueInputBuffer(index=%zu, offset=0, size=%zu, time=%" PRIu64
", flags=%u) #%d",
index, fragment->data.size(), timestamp_us, input_flag,
input_fragment_index_);
media_status_t status = AMediaCodec_queueInputBuffer(
codec_, index, 0 /* offset */, fragment->data.size(), timestamp_us,
input_flag);
if (status != AMEDIA_OK) {
ALOGE("Failed to queueInputBuffer: %d", status);
return false;
}
++input_fragment_index_;
return true;
}
bool MediaCodecDecoder::FeedEOSInputBuffer(size_t index) {
// Timestamp of EOS input buffer is undefined, use 0 here to test decoder
// robustness.
uint64_t timestamp_us = 0;
ALOGV("queueInputBuffer(index=%zu) EOS", index);
media_status_t status = AMediaCodec_queueInputBuffer(
codec_, index, 0 /* offset */, 0 /* size */, timestamp_us,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
if (status != AMEDIA_OK) {
ALOGE("Failed to queueInputBuffer EOS: %d", status);
return false;
}
return true;
}
bool MediaCodecDecoder::ReceiveOutputBuffer(size_t index,
const AMediaCodecBufferInfo& info) {
size_t out_size;
uint8_t* buf = AMediaCodec_getOutputBuffer(codec_, index, &out_size);
if (!buf) {
ALOGE("Failed to getOutputBuffer(index=%zu)", index);
return false;
}
received_outputs_++;
ALOGV(
"ReceiveOutputBuffer(index=%zu, size=%d, time=%" PRId64 ", flags=%u) #%d",
index, info.size, info.presentationTimeUs, info.flags, received_outputs_);
// Do not callback for dummy EOS output (info.size == 0)
if (output_buffer_ready_cb_ && info.size > 0) {
output_buffer_ready_cb_(buf, info.size);
}
media_status_t status =
AMediaCodec_releaseOutputBuffer(codec_, index, false /* render */);
if (status != AMEDIA_OK) {
ALOGE("Failed to releaseOutputBuffer(index=%zu): %d", index, status);
return false;
}
return true;
}
bool MediaCodecDecoder::GetOutputFormat() {
AMediaFormat* format = AMediaCodec_getOutputFormat(codec_);
bool success = true;
// Required formats
int32_t width = 0;
if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width)) {
ALOGE("Cannot find width in format.");
success = false;
}
int32_t height = 0;
if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height)) {
ALOGE("Cannot find height in format.");
success = false;
}
int32_t color_format = 0;
if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT,
&color_format)) {
ALOGE("Cannot find color-format in format.");
success = false;
}
// Optional formats
int32_t crop_left = 0;
int32_t crop_top = 0;
int32_t crop_right = width - 1;
int32_t crop_bottom = height - 1;
#if ANDROID_VERSION >= 0x0900 // Android 9.0 (Pie)
// Crop info is only avaiable on NDK version >= Pie.
if (!AMediaFormat_getRect(format, AMEDIAFORMAT_KEY_DISPLAY_CROP, &crop_left,
&crop_top, &crop_right, &crop_bottom)) {
ALOGD("Cannot find crop window in format. Set as large as frame size.");
crop_left = 0;
crop_top = 0;
crop_right = width - 1;
crop_bottom = height - 1;
}
#endif
// Note: For ARC++N, width and height are set as same as the size of crop
// window in ArcCodec. So the values above will be still satisfied in
// ARC++N.
// In current exiting ARC video decoder crop origin is always at (0,0)
if (crop_left != 0 || crop_top != 0) {
ALOGE("Crop origin is not (0,0): (%d,%d)", crop_left, crop_top);
success = false;
}
int32_t stride = 0;
if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_STRIDE, &stride)) {
ALOGD("Cannot find stride in format. Set as frame width.");
stride = width;
}
int32_t slice_height = 0;
if (!AMediaFormat_getInt32(format, FORMAT_KEY_SLICE_HEIGHT, &slice_height)) {
ALOGD("Cannot find slice-height in format. Set as frame height.");
slice_height = height;
}
if (output_format_changed_cb_) {
output_format_changed_cb_(
Size(stride, slice_height),
Size(crop_right - crop_left + 1, crop_bottom - crop_top + 1),
color_format);
}
return success;
}
} // namespace android