289 lines
11 KiB
C++
289 lines
11 KiB
C++
|
/*
|
||
|
* Copyright 2019 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 <memory>
|
||
|
#include <string>
|
||
|
|
||
|
#include "absl/debugging/failure_signal_handler.h"
|
||
|
#include "absl/debugging/symbolize.h"
|
||
|
#include "absl/flags/flag.h"
|
||
|
#include "absl/flags/parse.h"
|
||
|
#include "absl/strings/match.h"
|
||
|
#include "api/task_queue/default_task_queue_factory.h"
|
||
|
#include "api/test/create_frame_generator.h"
|
||
|
#include "api/test/frame_generator_interface.h"
|
||
|
#include "api/video/encoded_image.h"
|
||
|
#include "api/video/video_codec_type.h"
|
||
|
#include "api/video_codecs/video_codec.h"
|
||
|
#include "api/video_codecs/video_encoder.h"
|
||
|
#include "media/base/media_constants.h"
|
||
|
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
|
||
|
#include "modules/video_coding/codecs/vp8/include/vp8.h"
|
||
|
#include "modules/video_coding/codecs/vp9/include/vp9.h"
|
||
|
#include "modules/video_coding/include/video_error_codes.h"
|
||
|
#include "modules/video_coding/utility/ivf_file_writer.h"
|
||
|
#include "rtc_base/checks.h"
|
||
|
#include "rtc_base/event.h"
|
||
|
#include "rtc_base/logging.h"
|
||
|
#include "rtc_base/synchronization/mutex.h"
|
||
|
#include "rtc_base/system/file_wrapper.h"
|
||
|
#include "rtc_base/task_queue.h"
|
||
|
#include "test/testsupport/frame_reader.h"
|
||
|
#include "test/video_codec_settings.h"
|
||
|
|
||
|
#if defined(WEBRTC_USE_H264)
|
||
|
#include "modules/video_coding/codecs/h264/include/h264.h"
|
||
|
#endif
|
||
|
|
||
|
ABSL_FLAG(std::string, input, "", "Input YUV file to convert to IVF");
|
||
|
ABSL_FLAG(int, width, 0, "Input frame width");
|
||
|
ABSL_FLAG(int, height, 0, "Input frame height");
|
||
|
ABSL_FLAG(std::string, codec, cricket::kVp8CodecName, "Codec to use");
|
||
|
ABSL_FLAG(std::string, output, "", "Output IVF file");
|
||
|
|
||
|
namespace webrtc {
|
||
|
namespace test {
|
||
|
namespace {
|
||
|
|
||
|
constexpr int kMaxFramerate = 30;
|
||
|
// We use very big value here to ensure that codec won't hit any limits.
|
||
|
constexpr uint32_t kBitrateBps = 100000000;
|
||
|
constexpr int kKeyFrameIntervalMs = 30000;
|
||
|
constexpr int kMaxFrameEncodeWaitTimeoutMs = 2000;
|
||
|
constexpr int kFrameLogInterval = 100;
|
||
|
static const VideoEncoder::Capabilities kCapabilities(false);
|
||
|
|
||
|
class IvfFileWriterEncodedCallback : public EncodedImageCallback {
|
||
|
public:
|
||
|
IvfFileWriterEncodedCallback(const std::string& file_name,
|
||
|
VideoCodecType video_codec_type,
|
||
|
int expected_frames_count)
|
||
|
: file_writer_(
|
||
|
IvfFileWriter::Wrap(FileWrapper::OpenWriteOnly(file_name), 0)),
|
||
|
video_codec_type_(video_codec_type),
|
||
|
expected_frames_count_(expected_frames_count) {
|
||
|
RTC_CHECK(file_writer_.get());
|
||
|
}
|
||
|
~IvfFileWriterEncodedCallback() { RTC_CHECK(file_writer_->Close()); }
|
||
|
|
||
|
Result OnEncodedImage(const EncodedImage& encoded_image,
|
||
|
const CodecSpecificInfo* codec_specific_info,
|
||
|
const RTPFragmentationHeader* fragmentation) override {
|
||
|
RTC_CHECK(file_writer_->WriteFrame(encoded_image, video_codec_type_));
|
||
|
|
||
|
MutexLock lock(&lock_);
|
||
|
received_frames_count_++;
|
||
|
RTC_CHECK_LE(received_frames_count_, expected_frames_count_);
|
||
|
if (received_frames_count_ % kFrameLogInterval == 0) {
|
||
|
RTC_LOG(INFO) << received_frames_count_ << " out of "
|
||
|
<< expected_frames_count_ << " frames written";
|
||
|
}
|
||
|
next_frame_written_.Set();
|
||
|
return Result(Result::Error::OK);
|
||
|
}
|
||
|
|
||
|
void WaitNextFrameWritten(int timeout_ms) {
|
||
|
RTC_CHECK(next_frame_written_.Wait(timeout_ms));
|
||
|
next_frame_written_.Reset();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::unique_ptr<IvfFileWriter> file_writer_;
|
||
|
const VideoCodecType video_codec_type_;
|
||
|
const int expected_frames_count_;
|
||
|
|
||
|
Mutex lock_;
|
||
|
int received_frames_count_ RTC_GUARDED_BY(lock_) = 0;
|
||
|
rtc::Event next_frame_written_;
|
||
|
};
|
||
|
|
||
|
class Encoder {
|
||
|
public:
|
||
|
Encoder(int width,
|
||
|
int height,
|
||
|
int frames_count,
|
||
|
const std::string& output_file_name,
|
||
|
VideoCodecType video_codec_type,
|
||
|
std::unique_ptr<VideoEncoder> video_encoder)
|
||
|
: video_encoder_(std::move(video_encoder)),
|
||
|
task_queue_(CreateDefaultTaskQueueFactory()->CreateTaskQueue(
|
||
|
"Encoder",
|
||
|
TaskQueueFactory::Priority::HIGH)) {
|
||
|
ivf_writer_callback_ = std::make_unique<IvfFileWriterEncodedCallback>(
|
||
|
output_file_name, video_codec_type, frames_count);
|
||
|
|
||
|
task_queue_.PostTask([width, height, video_codec_type, this]() {
|
||
|
VideoCodec codec_settings;
|
||
|
CodecSettings(video_codec_type, &codec_settings);
|
||
|
codec_settings.width = width;
|
||
|
codec_settings.height = height;
|
||
|
codec_settings.maxFramerate = kMaxFramerate;
|
||
|
codec_settings.startBitrate = kBitrateBps;
|
||
|
codec_settings.minBitrate = kBitrateBps;
|
||
|
codec_settings.maxBitrate = kBitrateBps;
|
||
|
switch (video_codec_type) {
|
||
|
case VideoCodecType::kVideoCodecVP8: {
|
||
|
VideoCodecVP8* vp8_settings = codec_settings.VP8();
|
||
|
vp8_settings->frameDroppingOn = false;
|
||
|
vp8_settings->keyFrameInterval = kKeyFrameIntervalMs;
|
||
|
vp8_settings->denoisingOn = false;
|
||
|
} break;
|
||
|
case VideoCodecType::kVideoCodecVP9: {
|
||
|
VideoCodecVP9* vp9_settings = codec_settings.VP9();
|
||
|
vp9_settings->denoisingOn = false;
|
||
|
vp9_settings->frameDroppingOn = false;
|
||
|
vp9_settings->keyFrameInterval = kKeyFrameIntervalMs;
|
||
|
vp9_settings->automaticResizeOn = false;
|
||
|
} break;
|
||
|
case VideoCodecType::kVideoCodecH264: {
|
||
|
VideoCodecH264* h264_settings = codec_settings.H264();
|
||
|
h264_settings->frameDroppingOn = false;
|
||
|
h264_settings->keyFrameInterval = kKeyFrameIntervalMs;
|
||
|
} break;
|
||
|
default:
|
||
|
RTC_CHECK(false) << "Unsupported codec type";
|
||
|
}
|
||
|
VideoBitrateAllocation bitrate_allocation;
|
||
|
bitrate_allocation.SetBitrate(0, 0, kBitrateBps);
|
||
|
|
||
|
video_encoder_->RegisterEncodeCompleteCallback(
|
||
|
ivf_writer_callback_.get());
|
||
|
RTC_CHECK_EQ(
|
||
|
WEBRTC_VIDEO_CODEC_OK,
|
||
|
video_encoder_->InitEncode(
|
||
|
&codec_settings,
|
||
|
VideoEncoder::Settings(kCapabilities, /*number_of_cores=*/4,
|
||
|
/*max_payload_size=*/0)));
|
||
|
video_encoder_->SetRates(VideoEncoder::RateControlParameters(
|
||
|
bitrate_allocation,
|
||
|
static_cast<double>(codec_settings.maxFramerate)));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void Encode(const VideoFrame& frame) {
|
||
|
task_queue_.PostTask([frame, this]() {
|
||
|
RTC_CHECK_EQ(WEBRTC_VIDEO_CODEC_OK,
|
||
|
video_encoder_->Encode(frame, nullptr));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
void WaitNextFrameWritten(int timeout_ms) {
|
||
|
ivf_writer_callback_->WaitNextFrameWritten(timeout_ms);
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::unique_ptr<VideoEncoder> video_encoder_;
|
||
|
std::unique_ptr<IvfFileWriterEncodedCallback> ivf_writer_callback_;
|
||
|
|
||
|
rtc::TaskQueue task_queue_;
|
||
|
};
|
||
|
|
||
|
int GetFrameCount(std::string yuv_file_name, int width, int height) {
|
||
|
std::unique_ptr<FrameReader> yuv_reader =
|
||
|
std::make_unique<YuvFrameReaderImpl>(std::move(yuv_file_name), width,
|
||
|
height);
|
||
|
RTC_CHECK(yuv_reader->Init());
|
||
|
int frames_count = yuv_reader->NumberOfFrames();
|
||
|
yuv_reader->Close();
|
||
|
return frames_count;
|
||
|
}
|
||
|
|
||
|
VideoFrame BuildFrame(FrameGeneratorInterface::VideoFrameData frame_data,
|
||
|
uint32_t rtp_timestamp) {
|
||
|
return VideoFrame::Builder()
|
||
|
.set_video_frame_buffer(frame_data.buffer)
|
||
|
.set_update_rect(frame_data.update_rect)
|
||
|
.set_timestamp_rtp(rtp_timestamp)
|
||
|
.build();
|
||
|
}
|
||
|
|
||
|
void WriteVideoFile(std::string input_file_name,
|
||
|
int width,
|
||
|
int height,
|
||
|
std::string output_file_name,
|
||
|
VideoCodecType video_codec_type,
|
||
|
std::unique_ptr<VideoEncoder> video_encoder) {
|
||
|
int frames_count = GetFrameCount(input_file_name, width, height);
|
||
|
|
||
|
std::unique_ptr<FrameGeneratorInterface> frame_generator =
|
||
|
CreateFromYuvFileFrameGenerator({input_file_name}, width, height,
|
||
|
/*frame_repeat_count=*/1);
|
||
|
|
||
|
Encoder encoder(width, height, frames_count, output_file_name,
|
||
|
video_codec_type, std::move(video_encoder));
|
||
|
|
||
|
uint32_t last_frame_timestamp = 0;
|
||
|
|
||
|
for (int i = 0; i < frames_count; ++i) {
|
||
|
const uint32_t timestamp =
|
||
|
last_frame_timestamp + kVideoPayloadTypeFrequency / kMaxFramerate;
|
||
|
VideoFrame frame = BuildFrame(frame_generator->NextFrame(), timestamp);
|
||
|
|
||
|
last_frame_timestamp = timestamp;
|
||
|
|
||
|
encoder.Encode(frame);
|
||
|
encoder.WaitNextFrameWritten(kMaxFrameEncodeWaitTimeoutMs);
|
||
|
|
||
|
if ((i + 1) % kFrameLogInterval == 0) {
|
||
|
RTC_LOG(INFO) << i + 1 << " out of " << frames_count
|
||
|
<< " frames are sent for encoding";
|
||
|
}
|
||
|
}
|
||
|
RTC_LOG(INFO) << "All " << frames_count << " frame are sent for encoding";
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
} // namespace test
|
||
|
} // namespace webrtc
|
||
|
|
||
|
int main(int argc, char* argv[]) {
|
||
|
// Initialize the symbolizer to get a human-readable stack trace.
|
||
|
absl::InitializeSymbolizer(argv[0]);
|
||
|
|
||
|
absl::FailureSignalHandlerOptions options;
|
||
|
absl::InstallFailureSignalHandler(options);
|
||
|
|
||
|
absl::ParseCommandLine(argc, argv);
|
||
|
|
||
|
std::string codec_name = absl::GetFlag(FLAGS_codec);
|
||
|
std::string input_file_name = absl::GetFlag(FLAGS_input);
|
||
|
std::string output_file_name = absl::GetFlag(FLAGS_output);
|
||
|
int width = absl::GetFlag(FLAGS_width);
|
||
|
int height = absl::GetFlag(FLAGS_height);
|
||
|
RTC_CHECK_NE(input_file_name, "") << "--input is required";
|
||
|
RTC_CHECK_NE(output_file_name, "") << "--output is required";
|
||
|
RTC_CHECK_GT(width, 0) << "width must be greater then 0";
|
||
|
RTC_CHECK_GT(height, 0) << "height must be greater then 0";
|
||
|
if (absl::EqualsIgnoreCase(codec_name, cricket::kVp8CodecName)) {
|
||
|
webrtc::test::WriteVideoFile(
|
||
|
input_file_name, width, height, output_file_name,
|
||
|
webrtc::VideoCodecType::kVideoCodecVP8, webrtc::VP8Encoder::Create());
|
||
|
return 0;
|
||
|
}
|
||
|
if (absl::EqualsIgnoreCase(codec_name, cricket::kVp9CodecName)) {
|
||
|
webrtc::test::WriteVideoFile(
|
||
|
input_file_name, width, height, output_file_name,
|
||
|
webrtc::VideoCodecType::kVideoCodecVP9, webrtc::VP9Encoder::Create());
|
||
|
return 0;
|
||
|
}
|
||
|
#if defined(WEBRTC_USE_H264)
|
||
|
if (absl::EqualsIgnoreCase(codec_name, cricket::kH264CodecName)) {
|
||
|
webrtc::test::WriteVideoFile(
|
||
|
input_file_name, width, height, output_file_name,
|
||
|
webrtc::VideoCodecType::kVideoCodecH264,
|
||
|
webrtc::H264Encoder::Create(
|
||
|
cricket::VideoCodec(cricket::kH264CodecName)));
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
RTC_CHECK(false) << "Unsupported codec: " << codec_name;
|
||
|
return 1;
|
||
|
}
|