blob: 87313010db5d7f52af854d70aa1f1880d575c542 [file] [log] [blame]
// Copyright 2020 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_sender/simulated_capturer.h"
#include <algorithm>
#include <chrono>
#include <ratio>
#include <sstream>
#include <thread>
#include "cast/streaming/environment.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace cast {
using openscreen::operator<<; // To pretty-print chrono values.
namespace {
// Threshold at which a warning about media pausing should be logged.
constexpr std::chrono::seconds kPauseWarningThreshold{3};
} // namespace
SimulatedCapturer::Observer::~Observer() = default;
SimulatedCapturer::SimulatedCapturer(Environment* environment,
const char* path,
AVMediaType media_type,
Clock::time_point start_time,
Observer* observer)
: format_context_(MakeUniqueAVFormatContext(path)),
media_type_(media_type),
start_time_(start_time),
observer_(observer),
packet_(MakeUniqueAVPacket()),
decoded_frame_(MakeUniqueAVFrame()),
next_task_(environment->now_function(), environment->task_runner()) {
OSP_DCHECK(observer_);
if (!format_context_) {
OnError("MakeUniqueAVFormatContext", AVERROR_UNKNOWN);
return; // Capturer is halted (unable to start).
}
AVCodec* codec;
const int stream_result = av_find_best_stream(format_context_.get(),
media_type_, -1, -1, &codec, 0);
if (stream_result < 0) {
OnError("av_find_best_stream", stream_result);
return; // Capturer is halted (unable to start).
}
stream_index_ = stream_result;
decoder_context_ = MakeUniqueAVCodecContext(codec);
if (!decoder_context_) {
OnError("MakeUniqueAVCodecContext", AVERROR_BUG);
return; // Capturer is halted (unable to start).
}
// 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.
decoder_context_->thread_count =
std::min(std::max<int>(std::thread::hardware_concurrency(), 1), 8);
const int params_result = avcodec_parameters_to_context(
decoder_context_.get(),
format_context_->streams[stream_index_]->codecpar);
if (params_result < 0) {
OnError("avcodec_parameters_to_context", params_result);
return; // Capturer is halted (unable to start).
}
SetAdditionalDecoderParameters(decoder_context_.get());
const int open_result = avcodec_open2(decoder_context_.get(), codec, nullptr);
if (open_result < 0) {
OnError("avcodec_open2", open_result);
return; // Capturer is halted (unable to start).
}
next_task_.Schedule([this] { StartDecodingNextFrame(); },
Alarm::kImmediately);
}
SimulatedCapturer::~SimulatedCapturer() = default;
void SimulatedCapturer::SetAdditionalDecoderParameters(
AVCodecContext* decoder_context) {}
absl::optional<Clock::duration> SimulatedCapturer::ProcessDecodedFrame(
const AVFrame& frame) {
return Clock::duration::zero();
}
void SimulatedCapturer::OnError(const char* function_name, int av_errnum) {
// Make a human-readable string from the libavcodec error.
std::ostringstream error;
error << "For " << av_get_media_type_string(media_type_) << ", "
<< function_name << " returned error: " << av_err2str(av_errnum);
// Deliver the error notification in a separate task since this method might
// have been called from the constructor.
next_task_.Schedule(
[this, error_string = error.str()] {
observer_->OnError(this, error_string);
// Capturer is now halted.
},
Alarm::kImmediately);
}
// static
Clock::duration SimulatedCapturer::ToApproximateClockDuration(
int64_t ticks,
const AVRational& time_base) {
return Clock::duration(av_rescale_q(
ticks, time_base,
AVRational{Clock::duration::period::num, Clock::duration::period::den}));
}
void SimulatedCapturer::StartDecodingNextFrame() {
const int read_frame_result =
av_read_frame(format_context_.get(), packet_.get());
if (read_frame_result < 0) {
if (read_frame_result == AVERROR_EOF) {
// Insert a "flush request" into the decoder's pipeline, which will
// signal an EOF in ConsumeNextDecodedFrame() later.
avcodec_send_packet(decoder_context_.get(), nullptr);
next_task_.Schedule([this] { ConsumeNextDecodedFrame(); },
Alarm::kImmediately);
} else {
// All other error codes are fatal.
OnError("av_read_frame", read_frame_result);
// Capturer is now halted.
}
return;
}
if (packet_->stream_index != stream_index_) {
av_packet_unref(packet_.get());
next_task_.Schedule([this] { StartDecodingNextFrame(); },
Alarm::kImmediately);
return;
}
const int send_packet_result =
avcodec_send_packet(decoder_context_.get(), packet_.get());
av_packet_unref(packet_.get());
if (send_packet_result < 0) {
// Note: AVERROR(EAGAIN) is also treated as fatal here because
// avcodec_receive_frame() will be called repeatedly until its result code
// indicates avcodec_send_packet() must be called again.
OnError("avcodec_send_packet", send_packet_result);
return; // Capturer is now halted.
}
next_task_.Schedule([this] { ConsumeNextDecodedFrame(); },
Alarm::kImmediately);
}
void SimulatedCapturer::ConsumeNextDecodedFrame() {
const int receive_frame_result =
avcodec_receive_frame(decoder_context_.get(), decoded_frame_.get());
if (receive_frame_result < 0) {
switch (receive_frame_result) {
case AVERROR(EAGAIN):
// This result code, according to libavcodec documentation, means more
// data should be fed into the decoder (e.g., interframe dependencies).
next_task_.Schedule([this] { StartDecodingNextFrame(); },
Alarm::kImmediately);
return;
case AVERROR_EOF:
observer_->OnEndOfFile(this);
return; // Capturer is now halted.
default:
OnError("avcodec_receive_frame", receive_frame_result);
return; // Capturer is now halted.
}
}
const Clock::duration frame_timestamp = ToApproximateClockDuration(
decoded_frame_->best_effort_timestamp,
format_context_->streams[stream_index_]->time_base);
if (last_frame_timestamp_) {
const Clock::duration delta = frame_timestamp - *last_frame_timestamp_;
if (delta <= Clock::duration::zero()) {
OSP_LOG_WARN << "Dropping " << av_get_media_type_string(media_type_)
<< " frame with illegal timestamp (delta from last frame: "
<< delta << "). Bad media file!";
av_frame_unref(decoded_frame_.get());
next_task_.Schedule([this] { ConsumeNextDecodedFrame(); },
Alarm::kImmediately);
return;
} else if (delta >= kPauseWarningThreshold) {
OSP_LOG_INFO << "For " << av_get_media_type_string(media_type_)
<< ", encountered a media pause (" << delta
<< ") in the file.";
}
}
last_frame_timestamp_ = frame_timestamp;
Clock::time_point capture_time = start_time_ + frame_timestamp;
const auto delay_adjustment_or_null = ProcessDecodedFrame(*decoded_frame_);
if (!delay_adjustment_or_null) {
av_frame_unref(decoded_frame_.get());
return; // Stop. Fatal error occurred.
}
capture_time += *delay_adjustment_or_null;
next_task_.Schedule(
[this, capture_time] {
DeliverDataToClient(*decoded_frame_, capture_time);
av_frame_unref(decoded_frame_.get());
ConsumeNextDecodedFrame();
},
capture_time);
}
SimulatedAudioCapturer::Client::~Client() = default;
SimulatedAudioCapturer::SimulatedAudioCapturer(Environment* environment,
const char* path,
int num_channels,
int sample_rate,
Clock::time_point start_time,
Client* client)
: SimulatedCapturer(environment,
path,
AVMEDIA_TYPE_AUDIO,
start_time,
client),
num_channels_(num_channels),
sample_rate_(sample_rate),
client_(client),
resampler_(MakeUniqueSwrContext()) {
OSP_DCHECK_GT(num_channels_, 0);
OSP_DCHECK_GT(sample_rate_, 0);
}
SimulatedAudioCapturer::~SimulatedAudioCapturer() {
if (swr_is_initialized(resampler_.get())) {
swr_close(resampler_.get());
}
}
bool SimulatedAudioCapturer::EnsureResamplerIsInitializedFor(
const AVFrame& frame) {
if (swr_is_initialized(resampler_.get())) {
if (input_sample_format_ == static_cast<AVSampleFormat>(frame.format) &&
input_sample_rate_ == frame.sample_rate &&
input_channel_layout_ == frame.channel_layout) {
return true;
}
// Note: Usually, the resampler should be flushed before being destroyed.
// However, because of the way SimulatedAudioCapturer uses the API, only one
// audio sample should be dropped in the worst case. Log what's being
// dropped, just in case libswresample is behaving differently than
// expected.
const std::chrono::microseconds amount(
swr_get_delay(resampler_.get(), std::micro::den));
OSP_LOG_INFO << "Discarding " << amount
<< " of audio from the resampler before re-init.";
}
input_sample_format_ = AV_SAMPLE_FMT_NONE;
// Create a fake output frame to hold the output audio parameters, because the
// resampler API is weird that way.
const auto fake_output_frame = MakeUniqueAVFrame();
fake_output_frame->channel_layout =
av_get_default_channel_layout(num_channels_);
fake_output_frame->format = AV_SAMPLE_FMT_FLT;
fake_output_frame->sample_rate = sample_rate_;
const int config_result =
swr_config_frame(resampler_.get(), fake_output_frame.get(), &frame);
if (config_result < 0) {
OnError("swr_config_frame", config_result);
return false; // Capturer is now halted.
}
const int init_result = swr_init(resampler_.get());
if (init_result < 0) {
OnError("swr_init", init_result);
return false; // Capturer is now halted.
}
input_sample_format_ = static_cast<AVSampleFormat>(frame.format);
input_sample_rate_ = frame.sample_rate;
input_channel_layout_ = frame.channel_layout;
return true;
}
absl::optional<Clock::duration> SimulatedAudioCapturer::ProcessDecodedFrame(
const AVFrame& frame) {
if (!EnsureResamplerIsInitializedFor(frame)) {
return absl::nullopt;
}
const int64_t num_leftover_input_samples =
swr_get_delay(resampler_.get(), input_sample_rate_);
OSP_DCHECK_GE(num_leftover_input_samples, 0);
const Clock::duration capture_time_adjustment = -ToApproximateClockDuration(
num_leftover_input_samples, AVRational{1, input_sample_rate_});
const int64_t num_output_samples_desired =
av_rescale_rnd(num_leftover_input_samples + frame.nb_samples,
sample_rate_, input_sample_rate_, AV_ROUND_ZERO);
OSP_DCHECK_GE(num_output_samples_desired, 0);
resampled_audio_.resize(num_channels_ * num_output_samples_desired);
uint8_t* output_argument[1] = {
reinterpret_cast<uint8_t*>(resampled_audio_.data())};
const int num_samples_converted_or_error = swr_convert(
resampler_.get(), output_argument, num_output_samples_desired,
const_cast<const uint8_t**>(frame.extended_data), frame.nb_samples);
if (num_samples_converted_or_error < 0) {
resampled_audio_.clear();
swr_close(resampler_.get());
OnError("swr_convert", num_samples_converted_or_error);
return absl::nullopt; // Capturer is now halted.
}
resampled_audio_.resize(num_channels_ * num_samples_converted_or_error);
return capture_time_adjustment;
}
void SimulatedAudioCapturer::DeliverDataToClient(
const AVFrame& unused,
Clock::time_point capture_time) {
if (resampled_audio_.empty()) {
return;
}
client_->OnAudioData(resampled_audio_.data(),
resampled_audio_.size() / num_channels_, capture_time);
resampled_audio_.clear();
}
SimulatedVideoCapturer::Client::~Client() = default;
SimulatedVideoCapturer::SimulatedVideoCapturer(Environment* environment,
const char* path,
Clock::time_point start_time,
Client* client)
: SimulatedCapturer(environment,
path,
AVMEDIA_TYPE_VIDEO,
start_time,
client),
client_(client) {}
SimulatedVideoCapturer::~SimulatedVideoCapturer() = default;
void SimulatedVideoCapturer::SetAdditionalDecoderParameters(
AVCodecContext* decoder_context) {
// Require the I420 planar format for video.
decoder_context->get_format = [](struct AVCodecContext* s,
const enum AVPixelFormat* formats) {
// Return AV_PIX_FMT_YUV420P if it's in the provided list of supported
// formats. Otherwise, return AV_PIX_FMT_NONE.
//
// |formats| is a NONE-terminated array.
for (; *formats != AV_PIX_FMT_NONE; ++formats) {
if (*formats == AV_PIX_FMT_YUV420P) {
break;
}
}
return *formats;
};
}
void SimulatedVideoCapturer::DeliverDataToClient(
const AVFrame& frame,
Clock::time_point capture_time) {
client_->OnVideoFrame(frame, capture_time);
}
} // namespace cast
} // namespace openscreen