blob: 9a2324e312888e8d51dd9cad5d016e04175c0289 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cast/standalone_receiver/decoder.h"
#include <algorithm>
#include <sstream>
#include <thread>
#include "util/osp_logging.h"
#include "util/trace_logging.h"
namespace openscreen {
namespace cast {
Decoder::Buffer::Buffer() {
Resize(0);
}
Decoder::Buffer::~Buffer() = default;
void Decoder::Buffer::Resize(int new_size) {
const int padded_size = new_size + AV_INPUT_BUFFER_PADDING_SIZE;
if (static_cast<int>(buffer_.size()) == padded_size) {
return;
}
buffer_.resize(padded_size);
// libavcodec requires zero-padding the region at the end, as some decoders
// will treat this as a stop marker.
memset(buffer_.data() + new_size, 0, AV_INPUT_BUFFER_PADDING_SIZE);
}
absl::Span<const uint8_t> Decoder::Buffer::GetSpan() const {
return absl::Span<const uint8_t>(
buffer_.data(), buffer_.size() - AV_INPUT_BUFFER_PADDING_SIZE);
}
absl::Span<uint8_t> Decoder::Buffer::GetSpan() {
return absl::Span<uint8_t>(buffer_.data(),
buffer_.size() - AV_INPUT_BUFFER_PADDING_SIZE);
}
Decoder::Client::Client() = default;
Decoder::Client::~Client() = default;
Decoder::Decoder(const std::string& codec_name) : codec_name_(codec_name) {}
Decoder::~Decoder() = default;
void Decoder::Decode(FrameId frame_id, const Decoder::Buffer& buffer) {
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
if (!codec_ && !Initialize()) {
return;
}
// Parse the buffer for the required metadata and the packet to send to the
// decoder.
const absl::Span<const uint8_t> input = buffer.GetSpan();
const int bytes_consumed = av_parser_parse2(
parser_.get(), context_.get(), &packet_->data, &packet_->size,
input.data(), input.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (bytes_consumed < 0) {
OnError("av_parser_parse2", bytes_consumed, frame_id);
return;
}
if (!packet_->data) {
OnError("av_parser_parse2 found no packet", AVERROR_BUFFER_TOO_SMALL,
frame_id);
return;
}
// Send the packet to the decoder.
const int send_packet_result =
avcodec_send_packet(context_.get(), packet_.get());
if (send_packet_result < 0) {
// The result should not be EAGAIN because this code always pulls out all
// the decoded frames after feeding-in each AVPacket.
OSP_DCHECK_NE(send_packet_result, AVERROR(EAGAIN));
OnError("avcodec_send_packet", send_packet_result, frame_id);
return;
}
frames_decoding_.push_back(frame_id);
// Receive zero or more frames from the decoder.
for (;;) {
const int receive_frame_result =
avcodec_receive_frame(context_.get(), decoded_frame_.get());
if (receive_frame_result == AVERROR(EAGAIN)) {
break; // Decoder needs more input to produce another frame.
}
const FrameId decoded_frame_id = DidReceiveFrameFromDecoder();
if (receive_frame_result < 0) {
OnError("avcodec_receive_frame", receive_frame_result, decoded_frame_id);
return;
}
if (client_) {
client_->OnFrameDecoded(decoded_frame_id, *decoded_frame_);
}
av_frame_unref(decoded_frame_.get());
}
}
bool Decoder::Initialize() {
TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
// NOTE: The codec_name values found in OFFER messages, such as "vp8" or
// "h264" or "opus" are valid input strings to FFMPEG's look-up function, so
// no translation is required here.
codec_ = avcodec_find_decoder_by_name(codec_name_.c_str());
if (!codec_) {
HandleInitializationError("codec not available", AVERROR(EINVAL));
return false;
}
OSP_LOG_INFO << "Found codec: " << codec_name_ << " (known to FFMPEG as "
<< avcodec_get_name(codec_->id) << ')';
parser_ = MakeUniqueAVCodecParserContext(codec_->id);
if (!parser_) {
HandleInitializationError("failed to allocate parser context",
AVERROR(ENOMEM));
return false;
}
context_ = MakeUniqueAVCodecContext(codec_);
if (!context_) {
HandleInitializationError("failed to allocate codec context",
AVERROR(ENOMEM));
return false;
}
// This should always be greater than zero, so that decoding doesn't block the
// main thread of this receiver app and cause playback timing issues. The
// actual number should be tuned, based on the number of CPU cores.
//
// This should also be 16 or less, since the encoder implementations emit
// warnings about too many encode threads. FFMPEG's VP8 implementation
// actually silently freezes if this is 10 or more. Thus, 8 is used for the
// max here, just to be safe.
context_->thread_count =
std::min(std::max<int>(std::thread::hardware_concurrency(), 1), 8);
const int open_result = avcodec_open2(context_.get(), codec_, nullptr);
if (open_result < 0) {
HandleInitializationError("failed to open codec", open_result);
return false;
}
packet_ = MakeUniqueAVPacket();
if (!packet_) {
HandleInitializationError("failed to allocate AVPacket", AVERROR(ENOMEM));
return false;
}
decoded_frame_ = MakeUniqueAVFrame();
if (!decoded_frame_) {
HandleInitializationError("failed to allocate AVFrame", AVERROR(ENOMEM));
return false;
}
return true;
}
FrameId Decoder::DidReceiveFrameFromDecoder() {
const auto it = frames_decoding_.begin();
OSP_DCHECK(it != frames_decoding_.end());
const auto frame_id = *it;
frames_decoding_.erase(it);
return frame_id;
}
void Decoder::HandleInitializationError(const char* what, int av_errnum) {
// If the codec was found, get FFMPEG's canonical name for it.
const char* const canonical_name =
codec_ ? avcodec_get_name(codec_->id) : nullptr;
codec_ = nullptr; // Set null to mean "not initialized."
if (!client_) {
return; // Nowhere to emit error to, so don't bother.
}
std::ostringstream error;
error << "Could not initialize codec " << codec_name_;
if (canonical_name) {
error << " (known to FFMPEG as " << canonical_name << ')';
}
error << " because " << what << " (" << av_err2str(av_errnum) << ").";
client_->OnFatalError(error.str());
}
void Decoder::OnError(const char* what, int av_errnum, FrameId frame_id) {
if (!client_) {
return;
}
// Make a human-readable string from the libavcodec error.
std::ostringstream error;
if (!frame_id.is_null()) {
error << "frame: " << frame_id << "; ";
}
char human_readable_error[AV_ERROR_MAX_STRING_SIZE]{0};
av_make_error_string(human_readable_error, AV_ERROR_MAX_STRING_SIZE,
av_errnum);
error << "what: " << what << "; error: " << human_readable_error;
// Dispatch to either the fatal error handler, or the one for decode errors,
// as appropriate.
switch (av_errnum) {
case AVERROR_EOF:
case AVERROR(EINVAL):
case AVERROR(ENOMEM):
client_->OnFatalError(error.str());
break;
default:
client_->OnDecodeError(frame_id, error.str());
break;
}
}
} // namespace cast
} // namespace openscreen