Use a TaskQueue for decoding in VideoStreamDecoderImpl.
Long term goal is to use the VideoStreamDecoder in the VideoReceiveStream so that we can stop using legacy VideoCodingModule components and classes. This CL is one of several in preparation for that. Bug: webrtc:7408, webrtc:9378 Change-Id: Ifd7e4c3c7d38dbb7c4b0636aaad318c571a29158 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/164525 Reviewed-by: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Åsa Persson <asapersson@webrtc.org> Reviewed-by: Danil Chapovalov <danilchap@webrtc.org> Commit-Queue: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30211}
This commit is contained in:
@ -564,11 +564,13 @@ if (rtc_include_tests) {
|
|||||||
"video_receive_stream_unittest.cc",
|
"video_receive_stream_unittest.cc",
|
||||||
"video_send_stream_impl_unittest.cc",
|
"video_send_stream_impl_unittest.cc",
|
||||||
"video_send_stream_tests.cc",
|
"video_send_stream_tests.cc",
|
||||||
|
"video_stream_decoder_impl_unittest.cc",
|
||||||
"video_stream_encoder_unittest.cc",
|
"video_stream_encoder_unittest.cc",
|
||||||
]
|
]
|
||||||
deps = [
|
deps = [
|
||||||
":video",
|
":video",
|
||||||
":video_mocks",
|
":video_mocks",
|
||||||
|
":video_stream_decoder_impl",
|
||||||
":video_stream_encoder_impl",
|
":video_stream_encoder_impl",
|
||||||
"../api:create_frame_generator",
|
"../api:create_frame_generator",
|
||||||
"../api:fake_frame_decryptor",
|
"../api:fake_frame_decryptor",
|
||||||
|
|||||||
@ -24,48 +24,41 @@ VideoStreamDecoderImpl::VideoStreamDecoderImpl(
|
|||||||
VideoDecoderFactory* decoder_factory,
|
VideoDecoderFactory* decoder_factory,
|
||||||
TaskQueueFactory* task_queue_factory,
|
TaskQueueFactory* task_queue_factory,
|
||||||
std::map<int, std::pair<SdpVideoFormat, int>> decoder_settings)
|
std::map<int, std::pair<SdpVideoFormat, int>> decoder_settings)
|
||||||
: callbacks_(callbacks),
|
: timing_(Clock::GetRealTimeClock()),
|
||||||
|
decode_callbacks_(this),
|
||||||
|
next_frame_timestamps_index_(0),
|
||||||
|
callbacks_(callbacks),
|
||||||
|
keyframe_required_(true),
|
||||||
decoder_factory_(decoder_factory),
|
decoder_factory_(decoder_factory),
|
||||||
decoder_settings_(std::move(decoder_settings)),
|
decoder_settings_(std::move(decoder_settings)),
|
||||||
|
shut_down_(false),
|
||||||
|
frame_buffer_(Clock::GetRealTimeClock(), &timing_, nullptr),
|
||||||
bookkeeping_queue_(task_queue_factory->CreateTaskQueue(
|
bookkeeping_queue_(task_queue_factory->CreateTaskQueue(
|
||||||
"video_stream_decoder_bookkeeping_queue",
|
"video_stream_decoder_bookkeeping_queue",
|
||||||
TaskQueueFactory::Priority::NORMAL)),
|
TaskQueueFactory::Priority::NORMAL)),
|
||||||
decode_thread_(&DecodeLoop,
|
decode_queue_(task_queue_factory->CreateTaskQueue(
|
||||||
this,
|
"video_stream_decoder_decode_queue",
|
||||||
"video_stream_decoder_decode_thread",
|
TaskQueueFactory::Priority::NORMAL)) {
|
||||||
rtc::kHighestPriority),
|
|
||||||
timing_(Clock::GetRealTimeClock()),
|
|
||||||
frame_buffer_(Clock::GetRealTimeClock(), &timing_, nullptr),
|
|
||||||
next_frame_timestamps_index_(0) {
|
|
||||||
frame_timestamps_.fill({-1, -1, -1});
|
frame_timestamps_.fill({-1, -1, -1});
|
||||||
decode_thread_.Start();
|
bookkeeping_queue_.PostTask([this]() {
|
||||||
|
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
||||||
|
StartNextDecode();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStreamDecoderImpl::~VideoStreamDecoderImpl() {
|
VideoStreamDecoderImpl::~VideoStreamDecoderImpl() {
|
||||||
frame_buffer_.Stop();
|
rtc::CritScope lock(&shut_down_crit_);
|
||||||
decode_thread_.Stop();
|
shut_down_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStreamDecoderImpl::OnFrame(
|
void VideoStreamDecoderImpl::OnFrame(
|
||||||
std::unique_ptr<video_coding::EncodedFrame> frame) {
|
std::unique_ptr<video_coding::EncodedFrame> frame) {
|
||||||
if (!bookkeeping_queue_.IsCurrent()) {
|
if (!bookkeeping_queue_.IsCurrent()) {
|
||||||
struct OnFrameTask : QueuedTask {
|
bookkeeping_queue_.PostTask([this, frame = std::move(frame)]() mutable {
|
||||||
OnFrameTask(std::unique_ptr<video_coding::EncodedFrame> frame,
|
OnFrame(std::move(frame));
|
||||||
VideoStreamDecoderImpl* video_stream_decoder)
|
return true;
|
||||||
: frame_(std::move(frame)),
|
});
|
||||||
video_stream_decoder_(video_stream_decoder) {}
|
|
||||||
|
|
||||||
bool Run() override {
|
|
||||||
video_stream_decoder_->OnFrame(std::move(frame_));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<video_coding::EncodedFrame> frame_;
|
|
||||||
VideoStreamDecoderImpl* video_stream_decoder_;
|
|
||||||
};
|
|
||||||
|
|
||||||
bookkeeping_queue_.PostTask(
|
|
||||||
std::make_unique<OnFrameTask>(std::move(frame), this));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +113,8 @@ VideoDecoder* VideoStreamDecoderImpl::GetDecoder(int payload_type) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t register_result = decoder->RegisterDecodeCompleteCallback(this);
|
int32_t register_result =
|
||||||
|
decoder->RegisterDecodeCompleteCallback(&decode_callbacks_);
|
||||||
if (register_result != WEBRTC_VIDEO_CODEC_OK) {
|
if (register_result != WEBRTC_VIDEO_CODEC_OK) {
|
||||||
RTC_LOG(LS_WARNING) << "Failed to register decode callback.";
|
RTC_LOG(LS_WARNING) << "Failed to register decode callback.";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@ -131,102 +125,113 @@ VideoDecoder* VideoStreamDecoderImpl::GetDecoder(int payload_type) {
|
|||||||
return decoder_.get();
|
return decoder_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
void VideoStreamDecoderImpl::SaveFrameTimestamps(
|
||||||
void VideoStreamDecoderImpl::DecodeLoop(void* ptr) {
|
const video_coding::EncodedFrame& frame) {
|
||||||
// TODO(philipel): Remove this and use rtc::Event::kForever when it's
|
FrameTimestamps* frame_timestamps =
|
||||||
// supported by the |frame_buffer_|.
|
&frame_timestamps_[next_frame_timestamps_index_];
|
||||||
static constexpr int kForever = 100000000;
|
frame_timestamps->timestamp = frame.Timestamp();
|
||||||
|
frame_timestamps->decode_start_time_ms = rtc::TimeMillis();
|
||||||
|
frame_timestamps->render_time_us = frame.RenderTimeMs() * 1000;
|
||||||
|
|
||||||
int max_wait_time_ms = kForever;
|
next_frame_timestamps_index_ =
|
||||||
bool keyframe_required = true;
|
Add<kFrameTimestampsMemory>(next_frame_timestamps_index_, 1);
|
||||||
auto* vs_decoder = static_cast<VideoStreamDecoderImpl*>(ptr);
|
}
|
||||||
while (true) {
|
|
||||||
DecodeResult decode_result =
|
|
||||||
vs_decoder->DecodeNextFrame(max_wait_time_ms, keyframe_required);
|
|
||||||
|
|
||||||
switch (decode_result) {
|
void VideoStreamDecoderImpl::StartNextDecode() {
|
||||||
case kOk: {
|
int64_t max_wait_time = keyframe_required_ ? 200 : 3000;
|
||||||
max_wait_time_ms = kForever;
|
|
||||||
keyframe_required = false;
|
frame_buffer_.NextFrame(
|
||||||
break;
|
max_wait_time, keyframe_required_, &bookkeeping_queue_,
|
||||||
}
|
[this](std::unique_ptr<video_coding::EncodedFrame> frame,
|
||||||
case kDecodeFailure: {
|
video_coding::FrameBuffer::ReturnReason res) mutable {
|
||||||
max_wait_time_ms = 0;
|
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
||||||
keyframe_required = true;
|
OnNextFrameCallback(std::move(frame), res);
|
||||||
break;
|
});
|
||||||
}
|
}
|
||||||
case kNoFrame: {
|
|
||||||
max_wait_time_ms = kForever;
|
void VideoStreamDecoderImpl::OnNextFrameCallback(
|
||||||
// If we end up here it means that we got a decoding error and there is
|
std::unique_ptr<video_coding::EncodedFrame> frame,
|
||||||
// no keyframe available in the |frame_buffer_|.
|
video_coding::FrameBuffer::ReturnReason result) {
|
||||||
vs_decoder->bookkeeping_queue_.PostTask([vs_decoder]() {
|
switch (result) {
|
||||||
RTC_DCHECK_RUN_ON(&vs_decoder->bookkeeping_queue_);
|
case video_coding::FrameBuffer::kFrameFound: {
|
||||||
vs_decoder->callbacks_->OnNonDecodableState();
|
RTC_DCHECK(frame);
|
||||||
});
|
SaveFrameTimestamps(*frame);
|
||||||
break;
|
|
||||||
}
|
rtc::CritScope lock(&shut_down_crit_);
|
||||||
case kNoDecoder: {
|
if (shut_down_) {
|
||||||
max_wait_time_ms = kForever;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case kShutdown: {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
decode_queue_.PostTask([this, frame = std::move(frame)]() mutable {
|
||||||
|
RTC_DCHECK_RUN_ON(&decode_queue_);
|
||||||
|
DecodeResult decode_result = DecodeFrame(std::move(frame));
|
||||||
|
bookkeeping_queue_.PostTask([this, decode_result]() {
|
||||||
|
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
||||||
|
switch (decode_result) {
|
||||||
|
case kOk: {
|
||||||
|
keyframe_required_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kOkRequestKeyframe: {
|
||||||
|
callbacks_->OnNonDecodableState();
|
||||||
|
keyframe_required_ = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case kDecodeFailure: {
|
||||||
|
callbacks_->OnNonDecodableState();
|
||||||
|
keyframe_required_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StartNextDecode();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case video_coding::FrameBuffer::kTimeout: {
|
||||||
|
callbacks_->OnNonDecodableState();
|
||||||
|
// The |frame_buffer_| requires the frame callback function to complete
|
||||||
|
// before NextFrame is called again. For this reason we call
|
||||||
|
// StartNextDecode in a later task to allow this task to complete first.
|
||||||
|
bookkeeping_queue_.PostTask([this]() {
|
||||||
|
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
||||||
|
StartNextDecode();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case video_coding::FrameBuffer::kStopped: {
|
||||||
|
// We are shutting down, do nothing.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStreamDecoderImpl::DecodeResult VideoStreamDecoderImpl::DecodeNextFrame(
|
VideoStreamDecoderImpl::DecodeResult VideoStreamDecoderImpl::DecodeFrame(
|
||||||
int max_wait_time_ms,
|
std::unique_ptr<video_coding::EncodedFrame> frame) {
|
||||||
bool keyframe_required) {
|
RTC_DCHECK(frame);
|
||||||
std::unique_ptr<video_coding::EncodedFrame> frame;
|
|
||||||
video_coding::FrameBuffer::ReturnReason res =
|
|
||||||
frame_buffer_.NextFrame(max_wait_time_ms, &frame, keyframe_required);
|
|
||||||
|
|
||||||
if (res == video_coding::FrameBuffer::ReturnReason::kStopped)
|
VideoDecoder* decoder = GetDecoder(frame->PayloadType());
|
||||||
return kShutdown;
|
if (!decoder) {
|
||||||
|
return kDecodeFailure;
|
||||||
if (frame) {
|
|
||||||
VideoDecoder* decoder = GetDecoder(frame->PayloadType());
|
|
||||||
if (!decoder) {
|
|
||||||
RTC_LOG(LS_WARNING) << "Failed to get decoder, dropping frame ("
|
|
||||||
<< frame->id.picture_id << ":"
|
|
||||||
<< frame->id.spatial_layer << ").";
|
|
||||||
return kNoDecoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t decode_start_time_ms = rtc::TimeMillis();
|
|
||||||
int64_t timestamp = frame->Timestamp();
|
|
||||||
int64_t render_time_us = frame->RenderTimeMs() * 1000;
|
|
||||||
bookkeeping_queue_.PostTask(
|
|
||||||
[this, decode_start_time_ms, timestamp, render_time_us]() {
|
|
||||||
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
|
||||||
// Saving decode start time this way wont work if we decode spatial
|
|
||||||
// layers sequentially.
|
|
||||||
FrameTimestamps* frame_timestamps =
|
|
||||||
&frame_timestamps_[next_frame_timestamps_index_];
|
|
||||||
frame_timestamps->timestamp = timestamp;
|
|
||||||
frame_timestamps->decode_start_time_ms = decode_start_time_ms;
|
|
||||||
frame_timestamps->render_time_us = render_time_us;
|
|
||||||
|
|
||||||
next_frame_timestamps_index_ =
|
|
||||||
Add<kFrameTimestampsMemory>(next_frame_timestamps_index_, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
int32_t decode_result = decoder->Decode(frame->EncodedImage(),
|
|
||||||
false, // missing_frame
|
|
||||||
frame->RenderTimeMs());
|
|
||||||
|
|
||||||
return decode_result == WEBRTC_VIDEO_CODEC_OK ? kOk : kDecodeFailure;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return kNoFrame;
|
int32_t decode_result = decoder->Decode(frame->EncodedImage(), //
|
||||||
|
/*missing_frames=*/false, //
|
||||||
|
frame->RenderTimeMs());
|
||||||
|
switch (decode_result) {
|
||||||
|
case WEBRTC_VIDEO_CODEC_OK: {
|
||||||
|
return kOk;
|
||||||
|
}
|
||||||
|
case WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME: {
|
||||||
|
return kOkRequestKeyframe;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return kDecodeFailure;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStreamDecoderImpl::FrameTimestamps*
|
VideoStreamDecoderImpl::FrameTimestamps*
|
||||||
VideoStreamDecoderImpl::GetFrameTimestamps(int64_t timestamp) {
|
VideoStreamDecoderImpl::GetFrameTimestamps(int64_t timestamp) {
|
||||||
RTC_DCHECK_RUN_ON(&bookkeeping_queue_);
|
|
||||||
|
|
||||||
int start_time_index = next_frame_timestamps_index_;
|
int start_time_index = next_frame_timestamps_index_;
|
||||||
for (int i = 0; i < kFrameTimestampsMemory; ++i) {
|
for (int i = 0; i < kFrameTimestampsMemory; ++i) {
|
||||||
start_time_index = Subtract<kFrameTimestampsMemory>(start_time_index, 1);
|
start_time_index = Subtract<kFrameTimestampsMemory>(start_time_index, 1);
|
||||||
@ -238,23 +243,10 @@ VideoStreamDecoderImpl::GetFrameTimestamps(int64_t timestamp) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VideoDecoder::DecodedImageCallback
|
void VideoStreamDecoderImpl::OnDecodedFrameCallback(
|
||||||
int32_t VideoStreamDecoderImpl::Decoded(VideoFrame& decoded_image) {
|
VideoFrame& decoded_image,
|
||||||
Decoded(decoded_image, absl::nullopt, absl::nullopt);
|
absl::optional<int32_t> decode_time_ms,
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
absl::optional<uint8_t> qp) {
|
||||||
}
|
|
||||||
|
|
||||||
// VideoDecoder::DecodedImageCallback
|
|
||||||
int32_t VideoStreamDecoderImpl::Decoded(VideoFrame& decoded_image,
|
|
||||||
int64_t decode_time_ms) {
|
|
||||||
Decoded(decoded_image, decode_time_ms, absl::nullopt);
|
|
||||||
return WEBRTC_VIDEO_CODEC_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VideoDecoder::DecodedImageCallback
|
|
||||||
void VideoStreamDecoderImpl::Decoded(VideoFrame& decoded_image,
|
|
||||||
absl::optional<int32_t> decode_time_ms,
|
|
||||||
absl::optional<uint8_t> qp) {
|
|
||||||
int64_t decode_stop_time_ms = rtc::TimeMillis();
|
int64_t decode_stop_time_ms = rtc::TimeMillis();
|
||||||
|
|
||||||
bookkeeping_queue_.PostTask([this, decode_stop_time_ms, decoded_image,
|
bookkeeping_queue_.PostTask([this, decode_stop_time_ms, decoded_image,
|
||||||
@ -284,4 +276,28 @@ void VideoStreamDecoderImpl::Decoded(VideoFrame& decoded_image,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VideoStreamDecoderImpl::DecodeCallbacks::DecodeCallbacks(
|
||||||
|
VideoStreamDecoderImpl* video_stream_decoder_impl)
|
||||||
|
: video_stream_decoder_impl_(video_stream_decoder_impl) {}
|
||||||
|
|
||||||
|
int32_t VideoStreamDecoderImpl::DecodeCallbacks::Decoded(
|
||||||
|
VideoFrame& decoded_image) {
|
||||||
|
Decoded(decoded_image, absl::nullopt, absl::nullopt);
|
||||||
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t VideoStreamDecoderImpl::DecodeCallbacks::Decoded(
|
||||||
|
VideoFrame& decoded_image,
|
||||||
|
int64_t decode_time_ms) {
|
||||||
|
Decoded(decoded_image, decode_time_ms, absl::nullopt);
|
||||||
|
return WEBRTC_VIDEO_CODEC_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoStreamDecoderImpl::DecodeCallbacks::Decoded(
|
||||||
|
VideoFrame& decoded_image,
|
||||||
|
absl::optional<int32_t> decode_time_ms,
|
||||||
|
absl::optional<uint8_t> qp) {
|
||||||
|
video_stream_decoder_impl_->OnDecodedFrameCallback(decoded_image,
|
||||||
|
decode_time_ms, qp);
|
||||||
|
}
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
@ -26,8 +26,7 @@
|
|||||||
|
|
||||||
namespace webrtc {
|
namespace webrtc {
|
||||||
|
|
||||||
class VideoStreamDecoderImpl : public VideoStreamDecoderInterface,
|
class VideoStreamDecoderImpl : public VideoStreamDecoderInterface {
|
||||||
private DecodedImageCallback {
|
|
||||||
public:
|
public:
|
||||||
VideoStreamDecoderImpl(
|
VideoStreamDecoderImpl(
|
||||||
VideoStreamDecoderInterface::Callbacks* callbacks,
|
VideoStreamDecoderInterface::Callbacks* callbacks,
|
||||||
@ -43,12 +42,23 @@ class VideoStreamDecoderImpl : public VideoStreamDecoderInterface,
|
|||||||
void SetMaxPlayoutDelay(TimeDelta max_delay) override;
|
void SetMaxPlayoutDelay(TimeDelta max_delay) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
class DecodeCallbacks : public DecodedImageCallback {
|
||||||
|
public:
|
||||||
|
explicit DecodeCallbacks(VideoStreamDecoderImpl* video_stream_decoder_impl);
|
||||||
|
int32_t Decoded(VideoFrame& decodedImage) override;
|
||||||
|
int32_t Decoded(VideoFrame& decodedImage, int64_t decode_time_ms) override;
|
||||||
|
void Decoded(VideoFrame& decodedImage,
|
||||||
|
absl::optional<int32_t> decode_time_ms,
|
||||||
|
absl::optional<uint8_t> qp) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VideoStreamDecoderImpl* const video_stream_decoder_impl_;
|
||||||
|
};
|
||||||
|
|
||||||
enum DecodeResult {
|
enum DecodeResult {
|
||||||
kOk,
|
kOk,
|
||||||
|
kOkRequestKeyframe,
|
||||||
kDecodeFailure,
|
kDecodeFailure,
|
||||||
kNoFrame,
|
|
||||||
kNoDecoder,
|
|
||||||
kShutdown,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FrameTimestamps {
|
struct FrameTimestamps {
|
||||||
@ -57,36 +67,25 @@ class VideoStreamDecoderImpl : public VideoStreamDecoderInterface,
|
|||||||
int64_t render_time_us;
|
int64_t render_time_us;
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoDecoder* GetDecoder(int payload_type);
|
void SaveFrameTimestamps(const video_coding::EncodedFrame& frame)
|
||||||
static void DecodeLoop(void* ptr);
|
RTC_RUN_ON(bookkeeping_queue_);
|
||||||
DecodeResult DecodeNextFrame(int max_wait_time_ms, bool keyframe_required);
|
FrameTimestamps* GetFrameTimestamps(int64_t timestamp)
|
||||||
|
RTC_RUN_ON(bookkeeping_queue_);
|
||||||
|
void StartNextDecode() RTC_RUN_ON(bookkeeping_queue_);
|
||||||
|
void OnNextFrameCallback(std::unique_ptr<video_coding::EncodedFrame> frame,
|
||||||
|
video_coding::FrameBuffer::ReturnReason res)
|
||||||
|
RTC_RUN_ON(bookkeeping_queue_);
|
||||||
|
void OnDecodedFrameCallback(VideoFrame& decodedImage, // NOLINT
|
||||||
|
absl::optional<int32_t> decode_time_ms,
|
||||||
|
absl::optional<uint8_t> qp);
|
||||||
|
|
||||||
FrameTimestamps* GetFrameTimestamps(int64_t timestamp);
|
VideoDecoder* GetDecoder(int payload_type) RTC_RUN_ON(decode_queue_);
|
||||||
|
VideoStreamDecoderImpl::DecodeResult DecodeFrame(
|
||||||
|
std::unique_ptr<video_coding::EncodedFrame> frame)
|
||||||
|
RTC_RUN_ON(decode_queue_);
|
||||||
|
|
||||||
// Implements DecodedImageCallback interface
|
|
||||||
int32_t Decoded(VideoFrame& decodedImage) override;
|
|
||||||
int32_t Decoded(VideoFrame& decodedImage, int64_t decode_time_ms) override;
|
|
||||||
void Decoded(VideoFrame& decodedImage,
|
|
||||||
absl::optional<int32_t> decode_time_ms,
|
|
||||||
absl::optional<uint8_t> qp) override;
|
|
||||||
|
|
||||||
VideoStreamDecoderInterface::Callbacks* const callbacks_
|
|
||||||
RTC_PT_GUARDED_BY(bookkeeping_queue_);
|
|
||||||
VideoDecoderFactory* const decoder_factory_;
|
|
||||||
std::map<int, std::pair<SdpVideoFormat, int>> decoder_settings_;
|
|
||||||
|
|
||||||
// The |bookkeeping_queue_| is used to:
|
|
||||||
// - Make |callbacks_|.
|
|
||||||
// - Insert/extract frames from the |frame_buffer_|
|
|
||||||
// - Synchronize with whatever thread that makes the Decoded callback.
|
|
||||||
rtc::TaskQueue bookkeeping_queue_;
|
|
||||||
|
|
||||||
rtc::PlatformThread decode_thread_;
|
|
||||||
VCMTiming timing_;
|
VCMTiming timing_;
|
||||||
video_coding::FrameBuffer frame_buffer_;
|
DecodeCallbacks decode_callbacks_;
|
||||||
video_coding::VideoLayerFrameId last_continuous_id_;
|
|
||||||
absl::optional<int> current_payload_type_;
|
|
||||||
std::unique_ptr<VideoDecoder> decoder_;
|
|
||||||
|
|
||||||
// Some decoders are pipelined so it is not sufficient to save frame info
|
// Some decoders are pipelined so it is not sufficient to save frame info
|
||||||
// for the last frame only.
|
// for the last frame only.
|
||||||
@ -94,6 +93,31 @@ class VideoStreamDecoderImpl : public VideoStreamDecoderInterface,
|
|||||||
std::array<FrameTimestamps, kFrameTimestampsMemory> frame_timestamps_
|
std::array<FrameTimestamps, kFrameTimestampsMemory> frame_timestamps_
|
||||||
RTC_GUARDED_BY(bookkeeping_queue_);
|
RTC_GUARDED_BY(bookkeeping_queue_);
|
||||||
int next_frame_timestamps_index_ RTC_GUARDED_BY(bookkeeping_queue_);
|
int next_frame_timestamps_index_ RTC_GUARDED_BY(bookkeeping_queue_);
|
||||||
|
VideoStreamDecoderInterface::Callbacks* const callbacks_
|
||||||
|
RTC_PT_GUARDED_BY(bookkeeping_queue_);
|
||||||
|
video_coding::VideoLayerFrameId last_continuous_id_
|
||||||
|
RTC_GUARDED_BY(bookkeeping_queue_);
|
||||||
|
bool keyframe_required_ RTC_GUARDED_BY(bookkeeping_queue_);
|
||||||
|
|
||||||
|
absl::optional<int> current_payload_type_ RTC_GUARDED_BY(decode_queue_);
|
||||||
|
VideoDecoderFactory* const decoder_factory_ RTC_PT_GUARDED_BY(decode_queue_);
|
||||||
|
std::map<int, std::pair<SdpVideoFormat, int>> decoder_settings_
|
||||||
|
RTC_GUARDED_BY(decode_queue_);
|
||||||
|
|
||||||
|
// The |bookkeeping_queue_| use the |frame_buffer_| and also posts tasks to
|
||||||
|
// the |decode_queue_|. The |decode_queue_| in turn use the |decoder_| to
|
||||||
|
// decode frames. When the |decoder_| is done it will post back to the
|
||||||
|
// |bookkeeping_queue_| with the decoded frame. During shutdown we start by
|
||||||
|
// isolating the |bookkeeping_queue_| from the |decode_queue_|, so now it's
|
||||||
|
// safe for the |decode_queue_| to be destructed. After that the |decoder_|
|
||||||
|
// can be destructed, and then the |bookkeeping_queue_|. Finally the
|
||||||
|
// |frame_buffer_| can be destructed.
|
||||||
|
rtc::CriticalSection shut_down_crit_;
|
||||||
|
bool shut_down_ RTC_GUARDED_BY(shut_down_crit_);
|
||||||
|
video_coding::FrameBuffer frame_buffer_ RTC_GUARDED_BY(bookkeeping_queue_);
|
||||||
|
rtc::TaskQueue bookkeeping_queue_;
|
||||||
|
std::unique_ptr<VideoDecoder> decoder_ RTC_GUARDED_BY(decode_queue_);
|
||||||
|
rtc::TaskQueue decode_queue_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
232
video/video_stream_decoder_impl_unittest.cc
Normal file
232
video/video_stream_decoder_impl_unittest.cc
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by a BSD-style license
|
||||||
|
* that can be found in the LICENSE file in the root of the source
|
||||||
|
* tree. An additional intellectual property rights grant can be found
|
||||||
|
* in the file PATENTS. All contributing project authors may
|
||||||
|
* be found in the AUTHORS file in the root of the source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "video/video_stream_decoder_impl.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "api/video/i420_buffer.h"
|
||||||
|
#include "test/gmock.h"
|
||||||
|
#include "test/gtest.h"
|
||||||
|
#include "test/time_controller/simulated_time_controller.h"
|
||||||
|
|
||||||
|
namespace webrtc {
|
||||||
|
namespace {
|
||||||
|
using ::testing::_;
|
||||||
|
using ::testing::ByMove;
|
||||||
|
using ::testing::NiceMock;
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
class MockVideoStreamDecoderCallbacks
|
||||||
|
: public VideoStreamDecoderInterface::Callbacks {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD0(OnNonDecodableState, void());
|
||||||
|
MOCK_METHOD1(OnContinuousUntil,
|
||||||
|
void(const video_coding::VideoLayerFrameId& key));
|
||||||
|
MOCK_METHOD1(OnEncodedFrame, void(const video_coding::EncodedFrame& frame));
|
||||||
|
MOCK_METHOD3(OnDecodedFrame,
|
||||||
|
void(VideoFrame decodedImage,
|
||||||
|
absl::optional<int> decode_time_ms,
|
||||||
|
absl::optional<int> qp));
|
||||||
|
};
|
||||||
|
|
||||||
|
class StubVideoDecoder : public VideoDecoder {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD2(InitDecode,
|
||||||
|
int32_t(const VideoCodec* codec_settings,
|
||||||
|
int32_t number_of_cores));
|
||||||
|
|
||||||
|
int32_t Decode(const EncodedImage& input_image,
|
||||||
|
bool missing_frames,
|
||||||
|
int64_t render_time_ms) override {
|
||||||
|
int32_t ret_code = DecodeCall(input_image, missing_frames, render_time_ms);
|
||||||
|
if (ret_code == WEBRTC_VIDEO_CODEC_OK ||
|
||||||
|
ret_code == WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME) {
|
||||||
|
VideoFrame frame = VideoFrame::Builder()
|
||||||
|
.set_video_frame_buffer(I420Buffer::Create(1, 1))
|
||||||
|
.build();
|
||||||
|
callback_->Decoded(frame);
|
||||||
|
}
|
||||||
|
return ret_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOCK_METHOD3(DecodeCall,
|
||||||
|
int32_t(const EncodedImage& input_image,
|
||||||
|
bool missing_frames,
|
||||||
|
int64_t render_time_ms));
|
||||||
|
|
||||||
|
int32_t Release() override { return 0; }
|
||||||
|
|
||||||
|
int32_t RegisterDecodeCompleteCallback(
|
||||||
|
DecodedImageCallback* callback) override {
|
||||||
|
callback_ = callback;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DecodedImageCallback* callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WrappedVideoDecoder : public VideoDecoder {
|
||||||
|
public:
|
||||||
|
explicit WrappedVideoDecoder(StubVideoDecoder* decoder) : decoder_(decoder) {}
|
||||||
|
|
||||||
|
int32_t InitDecode(const VideoCodec* codec_settings,
|
||||||
|
int32_t number_of_cores) override {
|
||||||
|
return decoder_->InitDecode(codec_settings, number_of_cores);
|
||||||
|
}
|
||||||
|
int32_t Decode(const EncodedImage& input_image,
|
||||||
|
bool missing_frames,
|
||||||
|
int64_t render_time_ms) override {
|
||||||
|
return decoder_->Decode(input_image, missing_frames, render_time_ms);
|
||||||
|
}
|
||||||
|
int32_t Release() override { return decoder_->Release(); }
|
||||||
|
|
||||||
|
int32_t RegisterDecodeCompleteCallback(
|
||||||
|
DecodedImageCallback* callback) override {
|
||||||
|
return decoder_->RegisterDecodeCompleteCallback(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
StubVideoDecoder* decoder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeVideoDecoderFactory : public VideoDecoderFactory {
|
||||||
|
public:
|
||||||
|
std::vector<SdpVideoFormat> GetSupportedFormats() const override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::unique_ptr<VideoDecoder> CreateVideoDecoder(
|
||||||
|
const SdpVideoFormat& format) override {
|
||||||
|
if (format.name == "VP8") {
|
||||||
|
return std::make_unique<WrappedVideoDecoder>(&vp8_decoder_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.name == "AV1") {
|
||||||
|
return std::make_unique<WrappedVideoDecoder>(&av1_decoder_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
StubVideoDecoder& Vp8Decoder() { return vp8_decoder_; }
|
||||||
|
StubVideoDecoder& Av1Decoder() { return av1_decoder_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
NiceMock<StubVideoDecoder> vp8_decoder_;
|
||||||
|
NiceMock<StubVideoDecoder> av1_decoder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FakeEncodedFrame : public video_coding::EncodedFrame {
|
||||||
|
public:
|
||||||
|
int64_t ReceivedTime() const override { return 0; }
|
||||||
|
int64_t RenderTime() const override { return 0; }
|
||||||
|
|
||||||
|
// Setters for protected variables.
|
||||||
|
void SetPayloadType(int payload_type) { _payloadType = payload_type; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class FrameBuilder {
|
||||||
|
public:
|
||||||
|
FrameBuilder() : frame_(std::make_unique<FakeEncodedFrame>()) {}
|
||||||
|
|
||||||
|
FrameBuilder& WithPayloadType(int payload_type) {
|
||||||
|
frame_->SetPayloadType(payload_type);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameBuilder& WithPictureId(int picture_id) {
|
||||||
|
frame_->id.picture_id = picture_id;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<FakeEncodedFrame> Build() { return std::move(frame_); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<FakeEncodedFrame> frame_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class VideoStreamDecoderImplTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
VideoStreamDecoderImplTest()
|
||||||
|
: time_controller_(Timestamp::seconds(0)),
|
||||||
|
video_stream_decoder_(&callbacks_,
|
||||||
|
&decoder_factory_,
|
||||||
|
time_controller_.GetTaskQueueFactory(),
|
||||||
|
{{1, std::make_pair(SdpVideoFormat("VP8"), 1)},
|
||||||
|
{2, std::make_pair(SdpVideoFormat("AV1"), 1)}}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
NiceMock<MockVideoStreamDecoderCallbacks> callbacks_;
|
||||||
|
FakeVideoDecoderFactory decoder_factory_;
|
||||||
|
GlobalSimulatedTimeController time_controller_;
|
||||||
|
VideoStreamDecoderImpl video_stream_decoder_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, InsertAndDecodeFrame) {
|
||||||
|
video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build());
|
||||||
|
EXPECT_CALL(callbacks_, OnDecodedFrame);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, NonDecodableStateWaitingForKeyframe) {
|
||||||
|
EXPECT_CALL(callbacks_, OnNonDecodableState);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, NonDecodableStateWaitingForDeltaFrame) {
|
||||||
|
video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build());
|
||||||
|
EXPECT_CALL(callbacks_, OnDecodedFrame);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
EXPECT_CALL(callbacks_, OnNonDecodableState);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, InsertAndDecodeFrameWithKeyframeRequest) {
|
||||||
|
video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build());
|
||||||
|
EXPECT_CALL(decoder_factory_.Vp8Decoder(), DecodeCall)
|
||||||
|
.WillOnce(Return(WEBRTC_VIDEO_CODEC_OK_REQUEST_KEYFRAME));
|
||||||
|
EXPECT_CALL(callbacks_, OnDecodedFrame);
|
||||||
|
EXPECT_CALL(callbacks_, OnNonDecodableState);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, FailToInitDecoder) {
|
||||||
|
video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build());
|
||||||
|
ON_CALL(decoder_factory_.Vp8Decoder(), InitDecode)
|
||||||
|
.WillByDefault(Return(WEBRTC_VIDEO_CODEC_ERROR));
|
||||||
|
EXPECT_CALL(callbacks_, OnNonDecodableState);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, FailToDecodeFrame) {
|
||||||
|
video_stream_decoder_.OnFrame(FrameBuilder().WithPayloadType(1).Build());
|
||||||
|
ON_CALL(decoder_factory_.Vp8Decoder(), DecodeCall)
|
||||||
|
.WillByDefault(Return(WEBRTC_VIDEO_CODEC_ERROR));
|
||||||
|
EXPECT_CALL(callbacks_, OnNonDecodableState);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(VideoStreamDecoderImplTest, ChangeFramePayloadType) {
|
||||||
|
video_stream_decoder_.OnFrame(
|
||||||
|
FrameBuilder().WithPayloadType(1).WithPictureId(0).Build());
|
||||||
|
EXPECT_CALL(decoder_factory_.Vp8Decoder(), DecodeCall);
|
||||||
|
EXPECT_CALL(callbacks_, OnDecodedFrame);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
|
||||||
|
video_stream_decoder_.OnFrame(
|
||||||
|
FrameBuilder().WithPayloadType(2).WithPictureId(1).Build());
|
||||||
|
EXPECT_CALL(decoder_factory_.Av1Decoder(), DecodeCall);
|
||||||
|
EXPECT_CALL(callbacks_, OnDecodedFrame);
|
||||||
|
time_controller_.AdvanceTime(TimeDelta::ms(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace webrtc
|
||||||
Reference in New Issue
Block a user