| /* |
| * 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) 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; |
| } |