Index: webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.cc |
diff --git a/webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.cc b/webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.cc |
index 18eccb25a5d4c1950935b54a060b03ce8006250b..6da71c9bae757a46807b3ef4a5f18775f4dbeb30 100644 |
--- a/webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.cc |
+++ b/webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.cc |
@@ -11,8 +11,10 @@ |
#include "webrtc/modules/video_coding/codecs/h264/h264_encoder_impl.h" |
+#include <algorithm> |
#include <limits> |
+#include "webrtc/common_video/libyuv/include/scaler.h" |
#include "third_party/openh264/src/codec/api/svc/codec_api.h" |
#include "third_party/openh264/src/codec/api/svc/codec_app_def.h" |
#include "third_party/openh264/src/codec/api/svc/codec_def.h" |
@@ -21,6 +23,7 @@ |
#include "webrtc/base/logging.h" |
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h" |
#include "webrtc/system_wrappers/include/metrics.h" |
+#include "webrtc/video_frame.h" |
namespace webrtc { |
@@ -50,6 +53,78 @@ int NumberOfThreads(int width, int height, int number_of_cores) { |
return 1; |
} |
+std::vector<int> GetStreamBitratesKbps(const VideoCodec& codec, |
+ int bitrate_to_allocate_kbps) { |
+ if (codec.numberOfSimulcastStreams <= 1) { |
+ return std::vector<int>(1, bitrate_to_allocate_kbps); |
+ } |
+ |
+ std::vector<int> bitrates_kbps(codec.numberOfSimulcastStreams); |
+ // Allocate min -> target bitrates as long as we have bitrate to spend. |
+ size_t last_active_stream = 0; |
+ for (size_t i = 0; i < static_cast<size_t>(codec.numberOfSimulcastStreams) && |
+ bitrate_to_allocate_kbps >= |
+ static_cast<int>(codec.simulcastStream[i].minBitrate); |
+ ++i) { |
+ last_active_stream = i; |
+ int allocated_bitrate_kbps = |
+ std::min(static_cast<int>(codec.simulcastStream[i].targetBitrate), |
+ bitrate_to_allocate_kbps); |
+ bitrates_kbps[i] = allocated_bitrate_kbps; |
+ bitrate_to_allocate_kbps -= allocated_bitrate_kbps; |
+ } |
+ |
+ // Spend additional bits on the highest-quality active layer, up to max |
+ // bitrate. |
+ // TODO(pbos): Consider spending additional bits on last_active_stream-1 down |
+ // to 0 and not just the top layer when we have additional bitrate to spend. |
+ int allocated_bitrate_kbps = std::min( |
+ static_cast<int>(codec.simulcastStream[last_active_stream].maxBitrate - |
+ bitrates_kbps[last_active_stream]), |
+ bitrate_to_allocate_kbps); |
+ bitrates_kbps[last_active_stream] += allocated_bitrate_kbps; |
+ bitrate_to_allocate_kbps -= allocated_bitrate_kbps; |
+ |
+ // Make sure we can always send something. Suspending below min bitrate is |
+ // controlled outside the codec implementation and is not overriden by this. |
+ if (bitrates_kbps[0] < static_cast<int>(codec.simulcastStream[0].minBitrate)) |
+ bitrates_kbps[0] = static_cast<int>(codec.simulcastStream[0].minBitrate); |
+ |
+ return bitrates_kbps; |
+} |
+ |
+uint32_t SumStreamMaxBitrate(int streams, const VideoCodec& codec) { |
+ uint32_t bitrate_sum = 0; |
+ for (int i = 0; i < streams; ++i) { |
+ bitrate_sum += codec.simulcastStream[i].maxBitrate; |
+ } |
+ return bitrate_sum; |
+} |
+ |
+int NumberOfStreams(const VideoCodec& codec) { |
+ int streams = |
+ codec.numberOfSimulcastStreams < 1 ? 1 : codec.numberOfSimulcastStreams; |
+ uint32_t simulcast_max_bitrate = SumStreamMaxBitrate(streams, codec); |
+ if (simulcast_max_bitrate == 0) { |
+ streams = 1; |
+ } |
+ return streams; |
+} |
+ |
+bool ValidSimulcastResolutions(const VideoCodec& codec, int num_streams) { |
+ if (codec.width != codec.simulcastStream[num_streams - 1].width || |
+ codec.height != codec.simulcastStream[num_streams - 1].height) { |
+ return false; |
+ } |
+ for (int i = 0; i < num_streams; ++i) { |
+ if (codec.width * codec.simulcastStream[i].height != |
+ codec.height * codec.simulcastStream[i].width) { |
+ return false; |
+ } |
+ } |
+ return true; |
+} |
+ |
} // namespace |
static FrameType EVideoFrameType_to_FrameType(EVideoFrameType type) { |
@@ -148,16 +223,29 @@ static void RtpFragmentize(EncodedImage* encoded_image, |
} |
H264EncoderImpl::H264EncoderImpl() |
- : openh264_encoder_(nullptr), |
- encoded_image_callback_(nullptr), |
+ : encoded_image_callback_(nullptr), |
has_reported_init_(false), |
- has_reported_error_(false) { |
+ has_reported_error_(false), |
+ key_frame_request_(kMaxSimulcastStreams, false) { |
+ encoded_images_.reserve(kMaxSimulcastStreams); |
+ encoded_image_buffers_.reserve(kMaxSimulcastStreams); |
+ send_streams_.reserve(kMaxSimulcastStreams); |
+ encoders_.reserve(kMaxSimulcastStreams); |
+ scaled_input_frames_.reserve(kMaxSimulcastStreams); |
} |
H264EncoderImpl::~H264EncoderImpl() { |
Release(); |
} |
+void H264EncoderImpl::SetStreamState(bool send_stream, int stream_idx) { |
+ if (send_stream && !send_streams_[stream_idx]) { |
+ // Need a key frame if we have not sent this stream before. |
+ key_frame_request_[stream_idx] = true; |
+ } |
+ send_streams_[stream_idx] = send_stream; |
+} |
+ |
int32_t H264EncoderImpl::InitEncode(const VideoCodec* codec_settings, |
int32_t number_of_cores, |
size_t /*max_payload_size*/) { |
@@ -181,23 +269,27 @@ int32_t H264EncoderImpl::InitEncode(const VideoCodec* codec_settings, |
ReportError(); |
return release_ret; |
} |
- RTC_DCHECK(!openh264_encoder_); |
- // Create encoder. |
- if (WelsCreateSVCEncoder(&openh264_encoder_) != 0) { |
- // Failed to create encoder. |
- LOG(LS_ERROR) << "Failed to create OpenH264 encoder"; |
- RTC_DCHECK(!openh264_encoder_); |
- ReportError(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ int number_of_streams = NumberOfStreams(*codec_settings); |
+ bool doing_simulcast = (number_of_streams > 1); |
+ |
+ if (doing_simulcast && |
+ !ValidSimulcastResolutions(*codec_settings, number_of_streams)) { |
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
} |
- RTC_DCHECK(openh264_encoder_); |
- if (kOpenH264EncoderDetailedLogging) { |
- int trace_level = WELS_LOG_DETAIL; |
- openh264_encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, |
- &trace_level); |
+ // Code expects simulcastStream resolutions to be correct, make sure they are |
+ // filled even when there are no simulcast layers. |
+ if (codec_settings->numberOfSimulcastStreams == 0) { |
+ codec_settings_.simulcastStream[0].width = codec_settings->width; |
+ codec_settings_.simulcastStream[0].height = codec_settings->height; |
} |
- // else WELS_LOG_DEFAULT is used by default. |
+ |
+ encoded_images_.resize(number_of_streams); |
+ encoded_image_buffers_.resize(number_of_streams); |
+ encoders_.resize(number_of_streams); |
+ scaled_input_frames_.resize(number_of_streams); |
+ key_frame_request_.resize(number_of_streams); |
+ std::fill(key_frame_request_.begin(), key_frame_request_.end(), false); |
codec_settings_ = *codec_settings; |
if (codec_settings_.targetBitrate == 0) |
@@ -208,86 +300,127 @@ int32_t H264EncoderImpl::InitEncode(const VideoCodec* codec_settings, |
// memset(&p, 0, sizeof(SEncParamBase)) used in Initialize, and SEncParamExt |
// which is a superset of SEncParamBase (cleared with GetDefaultParams) used |
// in InitializeExt. |
- SEncParamExt init_params; |
- openh264_encoder_->GetDefaultParams(&init_params); |
- if (codec_settings_.mode == kRealtimeVideo) { |
- init_params.iUsageType = CAMERA_VIDEO_REAL_TIME; |
- } else if (codec_settings_.mode == kScreensharing) { |
- init_params.iUsageType = SCREEN_CONTENT_REAL_TIME; |
- } else { |
- ReportError(); |
- return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
- } |
- init_params.iPicWidth = codec_settings_.width; |
- init_params.iPicHeight = codec_settings_.height; |
- // |init_params| uses bit/s, |codec_settings_| uses kbit/s. |
- init_params.iTargetBitrate = codec_settings_.targetBitrate * 1000; |
- init_params.iMaxBitrate = codec_settings_.maxBitrate * 1000; |
- // Rate Control mode |
- init_params.iRCMode = RC_BITRATE_MODE; |
- init_params.fMaxFrameRate = static_cast<float>(codec_settings_.maxFramerate); |
- |
- // The following parameters are extension parameters (they're in SEncParamExt, |
- // not in SEncParamBase). |
- init_params.bEnableFrameSkip = |
- codec_settings_.codecSpecific.H264.frameDroppingOn; |
- // |uiIntraPeriod| - multiple of GOP size |
- // |keyFrameInterval| - number of frames |
- init_params.uiIntraPeriod = |
- codec_settings_.codecSpecific.H264.keyFrameInterval; |
- init_params.uiMaxNalSize = 0; |
- // Threading model: use auto. |
- // 0: auto (dynamic imp. internal encoder) |
- // 1: single thread (default value) |
- // >1: number of threads |
- init_params.iMultipleThreadIdc = NumberOfThreads(init_params.iPicWidth, |
- init_params.iPicHeight, |
- number_of_cores); |
- // The base spatial layer 0 is the only one we use. |
- init_params.sSpatialLayers[0].iVideoWidth = init_params.iPicWidth; |
- init_params.sSpatialLayers[0].iVideoHeight = init_params.iPicHeight; |
- init_params.sSpatialLayers[0].fFrameRate = init_params.fMaxFrameRate; |
- init_params.sSpatialLayers[0].iSpatialBitrate = init_params.iTargetBitrate; |
- init_params.sSpatialLayers[0].iMaxSpatialBitrate = init_params.iMaxBitrate; |
- // Slice num according to number of threads. |
- init_params.sSpatialLayers[0].sSliceCfg.uiSliceMode = SM_AUTO_SLICE; |
- |
- // Initialize. |
- if (openh264_encoder_->InitializeExt(&init_params) != 0) { |
- LOG(LS_ERROR) << "Failed to initialize OpenH264 encoder"; |
- Release(); |
- ReportError(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
+ |
+ for (int i = 0; i < number_of_streams; ++i) { |
+ // Create encoder. |
+ if (WelsCreateSVCEncoder(&encoders_[i]) != 0) { |
+ // Failed to create encoder. |
+ LOG(LS_ERROR) << "Failed to create OpenH264 encoder"; |
+ RTC_DCHECK(!encoders_[i]); |
+ ReportError(); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ |
+ RTC_DCHECK(encoders_[i]); |
+ if (kOpenH264EncoderDetailedLogging) { |
+ int trace_level = WELS_LOG_DETAIL; |
+ encoders_[i]->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level); |
+ } |
+ SEncParamExt init_params; |
+ memset(&init_params, 0, sizeof(SEncParamExt)); |
+ encoders_[i]->GetDefaultParams(&init_params); |
+ if (codec_settings_.mode == kRealtimeVideo) { |
+ init_params.iUsageType = CAMERA_VIDEO_REAL_TIME; |
+ } else if (codec_settings_.mode == kScreensharing) { |
+ init_params.iUsageType = SCREEN_CONTENT_REAL_TIME; |
+ } else { |
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
+ } |
+ init_params.iPicWidth = codec_settings_.simulcastStream[i].width; |
+ init_params.iPicHeight = codec_settings_.simulcastStream[i].height; |
+ // |init_params| uses bit/s, |codec_settings_| uses kbit/s. |
+ init_params.iTargetBitrate = codec_settings_.startBitrate * 1000; |
+ init_params.iMaxBitrate = codec_settings_.maxBitrate * 1000; |
+ // Rate Control mode |
+ init_params.iRCMode = RC_BITRATE_MODE; |
+ init_params.fMaxFrameRate = |
+ static_cast<float>(codec_settings_.maxFramerate); |
+ |
+ // The following parameters are extension parameters |
+ // (they're in SEncParamExt, not in SEncParamBase). |
+ init_params.bEnableFrameSkip = |
+ codec_settings_.codecSpecific.H264.frameDroppingOn; |
+ // |uiIntraPeriod| - multiple of GOP size |
+ // |keyFrameInterval| - number of frames |
+ init_params.uiIntraPeriod = |
+ codec_settings_.codecSpecific.H264.keyFrameInterval; |
+ init_params.uiMaxNalSize = 0; |
+ init_params.iComplexityMode = ECOMPLEXITY_MODE::LOW_COMPLEXITY; |
+ |
+ // Threading model: use auto. |
+ // 0: auto (dynamic imp. internal encoder) |
+ // 1: single thread (default value) |
+ // >1: number of threads |
+ init_params.iMultipleThreadIdc = NumberOfThreads( |
+ init_params.iPicWidth, init_params.iPicHeight, number_of_cores); |
+ // The base spatial layer 0 is the only one we use. |
+ init_params.sSpatialLayers[0].iVideoWidth = init_params.iPicWidth; |
+ init_params.sSpatialLayers[0].iVideoHeight = init_params.iPicHeight; |
+ init_params.sSpatialLayers[0].fFrameRate = init_params.fMaxFrameRate; |
+ init_params.sSpatialLayers[0].iSpatialBitrate = init_params.iTargetBitrate; |
+ init_params.sSpatialLayers[0].iMaxSpatialBitrate = init_params.iMaxBitrate; |
+ |
+ // Slice num according to number of threads. |
+ init_params.sSpatialLayers[0].sSliceCfg.uiSliceMode = SM_AUTO_SLICE; |
+ // Initialize. |
+ if (encoders_[i]->InitializeExt(&init_params) != 0) { |
+ LOG(LS_ERROR) << "Failed to initialize OpenH264 encoder"; |
+ Release(); |
+ ReportError(); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ int video_format = EVideoFormatType::videoFormatI420; |
+ encoders_[i]->SetOption(ENCODER_OPTION_DATAFORMAT, &video_format); |
+ // Initialize encoded image. Default buffer size: size of unencoded data. |
+ // allocate memory for encoded image |
+ if (encoded_images_[i]._buffer != NULL) { |
+ delete[] encoded_images_[i]._buffer; |
+ } |
+ encoded_images_[i]._size = |
+ CalcBufferSize(kI420, codec_settings->simulcastStream[i].width, |
+ codec_settings->simulcastStream[i].height); |
+ encoded_images_[i]._buffer = new uint8_t[encoded_images_[i]._size]; |
+ encoded_image_buffers_[i].reset(encoded_images_[i]._buffer); |
+ encoded_images_[i]._completeFrame = true; |
+ encoded_images_[i]._encodedWidth = 0; |
+ encoded_images_[i]._encodedHeight = 0; |
+ encoded_images_[i]._length = 0; |
+ |
+ // Initialize scaled input frames. |
+ scaled_input_frames_[i] = *new VideoFrame(); |
+ scaled_input_frames_[i].CreateEmptyFrame( |
+ codec_settings->simulcastStream[i].width, |
+ codec_settings->simulcastStream[i].height, |
+ CalculateYStrideSize(codec_settings->simulcastStream[i].width, |
+ codec_settings->simulcastStream[i].height), |
+ CalculateUVStrideSize(codec_settings->simulcastStream[i].width, |
+ codec_settings->simulcastStream[i].height), |
+ CalculateUVStrideSize(codec_settings->simulcastStream[i].width, |
+ codec_settings->simulcastStream[i].height)); |
} |
- int video_format = EVideoFormatType::videoFormatI420; |
- openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT, |
- &video_format); |
- |
- // Initialize encoded image. Default buffer size: size of unencoded data. |
- encoded_image_._size = CalcBufferSize( |
- kI420, codec_settings_.width, codec_settings_.height); |
- encoded_image_._buffer = new uint8_t[encoded_image_._size]; |
- encoded_image_buffer_.reset(encoded_image_._buffer); |
- encoded_image_._completeFrame = true; |
- encoded_image_._encodedWidth = 0; |
- encoded_image_._encodedHeight = 0; |
- encoded_image_._length = 0; |
return WEBRTC_VIDEO_CODEC_OK; |
} |
int32_t H264EncoderImpl::Release() { |
- if (openh264_encoder_) { |
- int uninit_ret = openh264_encoder_->Uninitialize(); |
- if (uninit_ret != 0) { |
- LOG(LS_WARNING) << "OpenH264 encoder's Uninitialize() returned " |
- << "unsuccessful: " << uninit_ret; |
+ while (!encoders_.empty()) { |
+ ISVCEncoder* openh264_encoder = encoders_.back(); |
+ if (openh264_encoder) { |
+ int uninit_ret = openh264_encoder->Uninitialize(); |
+ if (uninit_ret != 0) { |
+ LOG(LS_WARNING) << "OpenH264 encoder's Uninitialize() returned " |
+ << "unsuccessful: " << uninit_ret; |
+ } |
+ WelsDestroySVCEncoder(openh264_encoder); |
+ openh264_encoder = nullptr; |
+ encoders_.pop_back(); |
+ EncodedImage encoded_image = encoded_images_.back(); |
+ if (encoded_image._buffer != nullptr) { |
+ encoded_image._buffer = nullptr; |
+ encoded_image_buffers_.back().reset(); |
+ } |
+ encoded_images_.pop_back(); |
+ encoded_image_buffers_.pop_back(); |
} |
- WelsDestroySVCEncoder(openh264_encoder_); |
- openh264_encoder_ = nullptr; |
- } |
- if (encoded_image_._buffer != nullptr) { |
- encoded_image_._buffer = nullptr; |
- encoded_image_buffer_.reset(); |
} |
return WEBRTC_VIDEO_CODEC_OK; |
} |
@@ -302,21 +435,45 @@ int32_t H264EncoderImpl::SetRates(uint32_t bitrate, uint32_t framerate) { |
if (bitrate <= 0 || framerate <= 0) { |
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; |
} |
+ if (codec_settings_.maxBitrate > 0 && bitrate > codec_settings_.maxBitrate) { |
+ bitrate = codec_settings_.maxBitrate; |
+ } |
+ if (bitrate < codec_settings_.minBitrate) { |
+ bitrate = codec_settings_.minBitrate; |
+ } |
+ if (codec_settings_.numberOfSimulcastStreams > 0 && |
+ bitrate < codec_settings_.simulcastStream[0].minBitrate) { |
+ bitrate = codec_settings_.simulcastStream[0].minBitrate; |
+ } |
codec_settings_.targetBitrate = bitrate; |
codec_settings_.maxFramerate = framerate; |
- SBitrateInfo target_bitrate; |
- memset(&target_bitrate, 0, sizeof(SBitrateInfo)); |
- target_bitrate.iLayer = SPATIAL_LAYER_ALL, |
- target_bitrate.iBitrate = codec_settings_.targetBitrate * 1000; |
- openh264_encoder_->SetOption(ENCODER_OPTION_BITRATE, |
- &target_bitrate); |
- float max_framerate = static_cast<float>(codec_settings_.maxFramerate); |
- openh264_encoder_->SetOption(ENCODER_OPTION_FRAME_RATE, |
- &max_framerate); |
+ std::vector<int> stream_bitrates = |
+ GetStreamBitratesKbps(codec_settings_, bitrate); |
+ for (size_t i = 0; i < encoders_.size(); ++i) { |
+ SetStreamState(stream_bitrates[i] > 0, i); |
+ if (send_streams_[i]) { |
+ SBitrateInfo target_bitrate; |
+ memset(&target_bitrate, 0, sizeof(SBitrateInfo)); |
+ target_bitrate.iLayer = SPATIAL_LAYER_ALL, |
+ target_bitrate.iBitrate = stream_bitrates[i] * 1000; // bps |
+ encoders_[i]->SetOption(ENCODER_OPTION_BITRATE, &target_bitrate); |
+ float max_framerate = static_cast<float>(framerate); |
+ encoders_[i]->SetOption(ENCODER_OPTION_FRAME_RATE, &max_framerate); |
+ } |
+ } |
return WEBRTC_VIDEO_CODEC_OK; |
} |
+void H264EncoderImpl::Scale(const VideoFrame& input_frame, |
+ VideoFrame* output_frame) { |
+ Scaler scaler; |
+ scaler.Set(input_frame.width(), input_frame.height(), output_frame->width(), |
+ output_frame->height(), webrtc::kI420, webrtc::kI420, |
+ webrtc::kScaleBilinear); |
+ scaler.Scale(input_frame, output_frame); |
+} |
+ |
int32_t H264EncoderImpl::Encode( |
const VideoFrame& frame, const CodecSpecificInfo* codec_specific_info, |
const std::vector<FrameType>* frame_types) { |
@@ -334,88 +491,111 @@ int32_t H264EncoderImpl::Encode( |
ReportError(); |
return WEBRTC_VIDEO_CODEC_UNINITIALIZED; |
} |
- if (frame.width() != codec_settings_.width || |
- frame.height() != codec_settings_.height) { |
- LOG(LS_WARNING) << "Encoder initialized for " << codec_settings_.width |
- << "x" << codec_settings_.height << " but trying to encode " |
- << frame.width() << "x" << frame.height() << " frame."; |
- ReportError(); |
- return WEBRTC_VIDEO_CODEC_ERR_SIZE; |
- } |
- bool force_key_frame = false; |
- if (frame_types != nullptr) { |
- // We only support a single stream. |
- RTC_DCHECK_EQ(frame_types->size(), static_cast<size_t>(1)); |
- // Skip frame? |
- if ((*frame_types)[0] == kEmptyFrame) { |
- return WEBRTC_VIDEO_CODEC_OK; |
+ std::vector<bool> force_key_frame; |
+ force_key_frame.resize(encoders_.size()); |
+ std::fill(force_key_frame.begin(), force_key_frame.end(), false); |
+ for (size_t i = 0; i < key_frame_request_.size() && i < send_streams_.size(); |
+ ++i) { |
+ if (key_frame_request_[i] && send_streams_[i]) { |
+ force_key_frame[i] = true; |
} |
- // Force key frame? |
- force_key_frame = (*frame_types)[0] == kVideoFrameKey; |
} |
- if (force_key_frame) { |
- // API doc says ForceIntraFrame(false) does nothing, but calling this |
- // function forces a key frame regardless of the |bIDR| argument's value. |
- // (If every frame is a key frame we get lag/delays.) |
- openh264_encoder_->ForceIntraFrame(true); |
+ if (frame_types) { |
+ for (size_t i = 0; i < frame_types->size(); ++i) { |
+ if ((*frame_types)[i] == kVideoFrameKey) { |
+ force_key_frame[i] = true; |
+ } |
+ } |
} |
- // EncodeFrame input. |
- SSourcePicture picture; |
- memset(&picture, 0, sizeof(SSourcePicture)); |
- picture.iPicWidth = frame.width(); |
- picture.iPicHeight = frame.height(); |
- picture.iColorFormat = EVideoFormatType::videoFormatI420; |
- picture.uiTimeStamp = frame.ntp_time_ms(); |
- picture.iStride[0] = frame.stride(kYPlane); |
- picture.iStride[1] = frame.stride(kUPlane); |
- picture.iStride[2] = frame.stride(kVPlane); |
- picture.pData[0] = const_cast<uint8_t*>(frame.buffer(kYPlane)); |
- picture.pData[1] = const_cast<uint8_t*>(frame.buffer(kUPlane)); |
- picture.pData[2] = const_cast<uint8_t*>(frame.buffer(kVPlane)); |
- |
- // EncodeFrame output. |
- SFrameBSInfo info; |
- memset(&info, 0, sizeof(SFrameBSInfo)); |
- |
- // Encode! |
- int enc_ret = openh264_encoder_->EncodeFrame(&picture, &info); |
- if (enc_ret != 0) { |
- LOG(LS_ERROR) << "OpenH264 frame encoding failed, EncodeFrame returned " |
- << enc_ret << "."; |
- ReportError(); |
- return WEBRTC_VIDEO_CODEC_ERROR; |
- } |
+ for (size_t i = 0; i < encoders_.size(); ++i) { |
+ if (!send_streams_[i] || (*frame_types)[i] == kEmptyFrame) { |
+ continue; |
+ } |
+ // Scale input to match encode dimensions |
+ Scale(frame, &scaled_input_frames_[i]); |
+ |
+ if (scaled_input_frames_[i].width() != |
+ codec_settings_.simulcastStream[i].width || |
+ scaled_input_frames_[i].height() != |
+ codec_settings_.simulcastStream[i].height) { |
+ LOG(LS_ERROR) << "Encoder initialized for " |
+ << codec_settings_.simulcastStream[i].width << "x" |
+ << codec_settings_.simulcastStream[i].height |
+ << " but trying to encode " |
+ << scaled_input_frames_[i].width() << "x" |
+ << scaled_input_frames_[i].height() << " frame."; |
+ ReportError(); |
+ return WEBRTC_VIDEO_CODEC_ERR_SIZE; |
+ } |
+ |
+ SSourcePicture picture; |
+ memset(&picture, 0, sizeof(SSourcePicture)); |
+ picture.iPicWidth = scaled_input_frames_[i].width(); |
+ picture.iPicHeight = scaled_input_frames_[i].height(); |
+ picture.iColorFormat = EVideoFormatType::videoFormatI420; |
+ picture.uiTimeStamp = frame.ntp_time_ms(); |
+ picture.iStride[0] = scaled_input_frames_[i].stride(kYPlane); |
+ picture.iStride[1] = scaled_input_frames_[i].stride(kUPlane); |
+ picture.iStride[2] = scaled_input_frames_[i].stride(kVPlane); |
+ picture.pData[0] = |
+ const_cast<uint8_t*>(scaled_input_frames_[i].buffer(kYPlane)); |
+ picture.pData[1] = |
+ const_cast<uint8_t*>(scaled_input_frames_[i].buffer(kUPlane)); |
+ picture.pData[2] = |
+ const_cast<uint8_t*>(scaled_input_frames_[i].buffer(kVPlane)); |
+ if (force_key_frame[i]) { |
+ // API doc says ForceIntraFrame(false) does nothing, but calling this |
+ // function forces a key frame regardless of the |bIDR| argument's value. |
+ // (If every frame is a key frame we get lag/delays.) |
+ encoders_[i]->ForceIntraFrame(true); |
+ std::fill(key_frame_request_.begin(), key_frame_request_.end(), false); |
+ } |
+ // EncodeFrame output. |
+ SFrameBSInfo info; |
+ memset(&info, 0, sizeof(SFrameBSInfo)); |
+ int enc_ret = encoders_[i]->EncodeFrame(&picture, &info); |
+ if (enc_ret != 0) { |
+ LOG(LS_ERROR) << "OpenH264 frame encoding failed, EncodeFrame returned " |
+ << enc_ret << "."; |
+ ReportError(); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
- encoded_image_._encodedWidth = frame.width(); |
- encoded_image_._encodedHeight = frame.height(); |
- encoded_image_._timeStamp = frame.timestamp(); |
- encoded_image_.ntp_time_ms_ = frame.ntp_time_ms(); |
- encoded_image_.capture_time_ms_ = frame.render_time_ms(); |
- encoded_image_.rotation_ = frame.rotation(); |
- encoded_image_._frameType = EVideoFrameType_to_FrameType(info.eFrameType); |
- |
- // Split encoded image up into fragments. This also updates |encoded_image_|. |
- RTPFragmentationHeader frag_header; |
- RtpFragmentize(&encoded_image_, &encoded_image_buffer_, frame, &info, |
- &frag_header); |
- |
- // Encoder can skip frames to save bandwidth in which case |
- // |encoded_image_._length| == 0. |
- if (encoded_image_._length > 0) { |
- // Deliver encoded image. |
- CodecSpecificInfo codec_specific; |
- codec_specific.codecType = kVideoCodecH264; |
- encoded_image_callback_->Encoded(encoded_image_, |
- &codec_specific, |
- &frag_header); |
+ encoded_images_[i]._encodedWidth = codec_settings_.simulcastStream[i].width; |
+ encoded_images_[i]._encodedHeight = |
+ codec_settings_.simulcastStream[i].height; |
+ encoded_images_[i]._timeStamp = frame.timestamp(); |
+ encoded_images_[i].ntp_time_ms_ = frame.ntp_time_ms(); |
+ encoded_images_[i].capture_time_ms_ = frame.render_time_ms(); |
+ encoded_images_[i]._frameType = |
+ EVideoFrameType_to_FrameType(info.eFrameType); |
+ // Split encoded image up into fragments. This also updates |
+ // |encoded_image_|. |
+ RTPFragmentationHeader frag_header; |
+ RtpFragmentize(&encoded_images_[i], &encoded_image_buffers_[i], frame, |
+ &info, &frag_header); |
+ if (encoded_images_[i]._length > 0) { |
+ // Deliver encoded image. |
+ CodecSpecificInfo codec_specific; |
+ CodecSpecificInfoH264* h264Info = &(codec_specific.codecSpecific.H264); |
+ h264Info->simulcastIdx = i; |
+ codec_specific.codecType = kVideoCodecH264; |
+ encoded_image_callback_->Encoded(encoded_images_[i], &codec_specific, |
+ &frag_header); |
+ } |
} |
return WEBRTC_VIDEO_CODEC_OK; |
} |
bool H264EncoderImpl::IsInitialized() const { |
- return openh264_encoder_ != nullptr; |
+ for (auto openh264_encoder : encoders_) { |
+ if (openh264_encoder == nullptr) { |
+ return false; |
+ } |
+ } |
+ return true; |
} |
void H264EncoderImpl::ReportInit() { |