123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- #include <limits>
- #include <string>
- #include "absl/strings/match.h"
- #include "common_video/libyuv/include/webrtc_libyuv.h"
- #include "modules/video_coding/utility/simulcast_rate_allocator.h"
- #include "modules/video_coding/utility/simulcast_utility.h"
- #include "rtc_base/checks.h"
- #include "rtc_base/logging.h"
- #include "rtc_base/time_utils.h"
- #include "system_wrappers/include/metrics.h"
- #include "third_party/libyuv/include/libyuv/convert.h"
- #include "third_party/libyuv/include/libyuv/scale.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"
- #include "third_party/openh264/src/codec/api/svc/codec_ver.h"
- #include "openh264_imp.h"
- namespace webrtc {
- namespace {
- const bool kOpenH264EncoderDetailedLogging = false;
- static const int kLowH264QpThreshold = 24;
- static const int kHighH264QpThreshold = 37;
- enum H264EncoderImplEvent {
- kH264EncoderEventInit = 0,
- kH264EncoderEventError = 1,
- kH264EncoderEventMax = 16,
- };
- int NumberOfThreads(int width, int height, int number_of_cores) {
-
-
-
-
-
-
-
-
-
-
-
-
-
- return 1;
- }
- VideoFrameType ConvertToVideoFrameType(EVideoFrameType type) {
- switch (type) {
- case videoFrameTypeIDR:
- return VideoFrameType::kVideoFrameKey;
- case videoFrameTypeSkip:
- case videoFrameTypeI:
- case videoFrameTypeP:
- case videoFrameTypeIPMixed:
- return VideoFrameType::kVideoFrameDelta;
- case videoFrameTypeInvalid:
- break;
- }
- RTC_NOTREACHED() << "Unexpected/invalid frame type: " << type;
- return VideoFrameType::kEmptyFrame;
- }
- }
- static void RtpFragmentize(EncodedImage* encoded_image, SFrameBSInfo* info) {
-
- size_t required_capacity = 0;
- size_t fragments_count = 0;
- for (int layer = 0; layer < info->iLayerNum; ++layer) {
- const SLayerBSInfo& layerInfo = info->sLayerInfo[layer];
- for (int nal = 0; nal < layerInfo.iNalCount; ++nal, ++fragments_count) {
- RTC_CHECK_GE(layerInfo.pNalLengthInByte[nal], 0);
-
- RTC_CHECK_LE(layerInfo.pNalLengthInByte[nal],
- std::numeric_limits<size_t>::max() - required_capacity);
- required_capacity += layerInfo.pNalLengthInByte[nal];
- }
- }
-
- auto buffer = EncodedImageBuffer::Create(required_capacity);
- encoded_image->SetEncodedData(buffer);
-
-
- const uint8_t start_code[4] = {0, 0, 0, 1};
- size_t frag = 0;
- encoded_image->set_size(0);
- for (int layer = 0; layer < info->iLayerNum; ++layer) {
- const SLayerBSInfo& layerInfo = info->sLayerInfo[layer];
-
- size_t layer_len = 0;
- for (int nal = 0; nal < layerInfo.iNalCount; ++nal, ++frag) {
-
-
- RTC_DCHECK_GE(layerInfo.pNalLengthInByte[nal], 4);
- RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len + 0], start_code[0]);
- RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len + 1], start_code[1]);
- RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len + 2], start_code[2]);
- RTC_DCHECK_EQ(layerInfo.pBsBuf[layer_len + 3], start_code[3]);
- layer_len += layerInfo.pNalLengthInByte[nal];
- }
-
- memcpy(buffer->data() + encoded_image->size(), layerInfo.pBsBuf, layer_len);
- encoded_image->set_size(encoded_image->size() + layer_len);
- }
- }
- H264EncoderImpl::H264EncoderImpl(const cricket::VideoCodec& codec)
- : packetization_mode_(H264PacketizationMode::SingleNalUnit),
- max_payload_size_(0),
- number_of_cores_(0),
- encoded_image_callback_(nullptr),
- has_reported_init_(false),
- has_reported_error_(false) {
- RTC_CHECK(absl::EqualsIgnoreCase(codec.name, cricket::kH264CodecName));
- std::string packetization_mode_string;
- if (codec.GetParam(cricket::kH264FmtpPacketizationMode,
- &packetization_mode_string) &&
- packetization_mode_string == "1") {
- packetization_mode_ = H264PacketizationMode::NonInterleaved;
- }
- downscaled_buffers_.reserve(kMaxSimulcastStreams - 1);
- encoded_images_.reserve(kMaxSimulcastStreams);
- encoders_.reserve(kMaxSimulcastStreams);
- configurations_.reserve(kMaxSimulcastStreams);
- tl0sync_limit_.reserve(kMaxSimulcastStreams);
- }
- H264EncoderImpl::~H264EncoderImpl() {
- Release();
- }
- int32_t H264EncoderImpl::InitEncode(const VideoCodec* inst,
- const VideoEncoder::Settings& settings) {
- ReportInit();
- if (!inst || inst->codecType != kVideoCodecH264) {
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
- }
- if (inst->maxFramerate == 0) {
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
- }
- if (inst->width < 1 || inst->height < 1) {
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
- }
- int32_t release_ret = Release();
- if (release_ret != WEBRTC_VIDEO_CODEC_OK) {
- ReportError();
- return release_ret;
- }
- int number_of_streams = SimulcastUtility::NumberOfSimulcastStreams(*inst);
- bool doing_simulcast = (number_of_streams > 1);
- if (doing_simulcast &&
- !SimulcastUtility::ValidSimulcastParameters(*inst, number_of_streams)) {
- return WEBRTC_VIDEO_CODEC_ERR_SIMULCAST_PARAMETERS_NOT_SUPPORTED;
- }
- downscaled_buffers_.resize(number_of_streams - 1);
- encoded_images_.resize(number_of_streams);
- encoders_.resize(number_of_streams);
- pictures_.resize(number_of_streams);
- configurations_.resize(number_of_streams);
- tl0sync_limit_.resize(number_of_streams);
- number_of_cores_ = settings.number_of_cores;
- max_payload_size_ = settings.max_payload_size;
- codec_ = *inst;
-
-
- if (codec_.numberOfSimulcastStreams == 0) {
- codec_.simulcastStream[0].width = codec_.width;
- codec_.simulcastStream[0].height = codec_.height;
- }
- for (int i = 0, idx = number_of_streams - 1; i < number_of_streams;
- ++i, --idx) {
- ISVCEncoder* openh264_encoder;
-
- if (WelsCreateSVCEncoder(&openh264_encoder) != 0) {
-
- RTC_LOG(LS_ERROR) << "Failed to create OpenH264 encoder";
- RTC_DCHECK(!openh264_encoder);
- Release();
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERROR;
- }
- RTC_DCHECK(openh264_encoder);
- if (kOpenH264EncoderDetailedLogging) {
- int trace_level = WELS_LOG_DETAIL;
- openh264_encoder->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level);
- }
-
-
- encoders_[i] = openh264_encoder;
-
- configurations_[i].simulcast_idx = idx;
- configurations_[i].sending = false;
- configurations_[i].width = codec_.simulcastStream[idx].width;
- configurations_[i].height = codec_.simulcastStream[idx].height;
- configurations_[i].max_frame_rate = static_cast<float>(codec_.maxFramerate);
- configurations_[i].frame_dropping_on = codec_.H264()->frameDroppingOn;
- configurations_[i].key_frame_interval = codec_.H264()->keyFrameInterval;
- configurations_[i].num_temporal_layers =
- codec_.simulcastStream[idx].numberOfTemporalLayers;
-
- if (i > 0) {
- downscaled_buffers_[i - 1] = I420Buffer::Create(
- configurations_[i].width, configurations_[i].height,
- configurations_[i].width, configurations_[i].width / 2,
- configurations_[i].width / 2);
- }
-
- configurations_[i].max_bps = codec_.maxBitrate * 1000;
- configurations_[i].target_bps = codec_.startBitrate * 1000;
-
- SEncParamExt encoder_params = CreateEncoderParams(i);
-
- if (openh264_encoder->InitializeExt(&encoder_params) != 0) {
- RTC_LOG(LS_ERROR) << "Failed to initialize OpenH264 encoder";
- Release();
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERROR;
- }
-
- int video_format = EVideoFormatType::videoFormatI420;
- openh264_encoder->SetOption(ENCODER_OPTION_DATAFORMAT, &video_format);
-
- const size_t new_capacity =
- CalcBufferSize(VideoType::kI420, codec_.simulcastStream[idx].width,
- codec_.simulcastStream[idx].height);
- encoded_images_[i].SetEncodedData(EncodedImageBuffer::Create(new_capacity));
- encoded_images_[i]._completeFrame = true;
- encoded_images_[i]._encodedWidth = codec_.simulcastStream[idx].width;
- encoded_images_[i]._encodedHeight = codec_.simulcastStream[idx].height;
- encoded_images_[i].set_size(0);
- tl0sync_limit_[i] = configurations_[i].num_temporal_layers;
- }
- SimulcastRateAllocator init_allocator(codec_);
- VideoBitrateAllocation allocation =
- init_allocator.Allocate(VideoBitrateAllocationParameters(
- DataRate::KilobitsPerSec(codec_.startBitrate), codec_.maxFramerate));
- SetRates(RateControlParameters(allocation, codec_.maxFramerate));
- return WEBRTC_VIDEO_CODEC_OK;
- }
- int32_t H264EncoderImpl::Release() {
- while (!encoders_.empty()) {
- ISVCEncoder* openh264_encoder = encoders_.back();
- if (openh264_encoder) {
- RTC_CHECK_EQ(0, openh264_encoder->Uninitialize());
- WelsDestroySVCEncoder(openh264_encoder);
- }
- encoders_.pop_back();
- }
- downscaled_buffers_.clear();
- configurations_.clear();
- encoded_images_.clear();
- pictures_.clear();
- tl0sync_limit_.clear();
- return WEBRTC_VIDEO_CODEC_OK;
- }
- int32_t H264EncoderImpl::RegisterEncodeCompleteCallback(
- EncodedImageCallback* callback) {
- encoded_image_callback_ = callback;
- return WEBRTC_VIDEO_CODEC_OK;
- }
- void H264EncoderImpl::SetRates(const RateControlParameters& parameters) {
- if (encoders_.empty()) {
- RTC_LOG(LS_WARNING) << "SetRates() while uninitialized.";
- return;
- }
- if (parameters.framerate_fps < 1.0) {
- RTC_LOG(LS_WARNING) << "Invalid frame rate: " << parameters.framerate_fps;
- return;
- }
- if (parameters.bitrate.get_sum_bps() == 0) {
-
- for (size_t i = 0; i < configurations_.size(); ++i) {
- configurations_[i].SetStreamState(false);
- }
- return;
- }
- codec_.maxFramerate = static_cast<uint32_t>(parameters.framerate_fps);
- size_t stream_idx = encoders_.size() - 1;
- for (size_t i = 0; i < encoders_.size(); ++i, --stream_idx) {
-
- configurations_[i].target_bps =
- parameters.bitrate.GetSpatialLayerSum(stream_idx);
- configurations_[i].max_frame_rate = parameters.framerate_fps;
- if (configurations_[i].target_bps) {
- configurations_[i].SetStreamState(true);
-
- SBitrateInfo target_bitrate;
- memset(&target_bitrate, 0, sizeof(SBitrateInfo));
- target_bitrate.iLayer = SPATIAL_LAYER_ALL,
- target_bitrate.iBitrate = configurations_[i].target_bps;
- encoders_[i]->SetOption(ENCODER_OPTION_BITRATE, &target_bitrate);
- encoders_[i]->SetOption(ENCODER_OPTION_FRAME_RATE,
- &configurations_[i].max_frame_rate);
- } else {
- configurations_[i].SetStreamState(false);
- }
- }
- }
- int32_t H264EncoderImpl::Encode(
- const VideoFrame& input_frame,
- const std::vector<VideoFrameType>* frame_types) {
- if (encoders_.empty()) {
- ReportError();
- return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
- }
- if (!encoded_image_callback_) {
- RTC_LOG(LS_WARNING)
- << "InitEncode() has been called, but a callback function "
- "has not been set with RegisterEncodeCompleteCallback()";
- ReportError();
- return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
- }
- rtc::scoped_refptr<const I420BufferInterface> frame_buffer =
- input_frame.video_frame_buffer()->ToI420();
- bool send_key_frame = false;
- for (size_t i = 0; i < configurations_.size(); ++i) {
- if (configurations_[i].key_frame_request && configurations_[i].sending) {
- send_key_frame = true;
- break;
- }
- }
- if (!send_key_frame && frame_types) {
- for (size_t i = 0; i < configurations_.size(); ++i) {
- const size_t simulcast_idx =
- static_cast<size_t>(configurations_[i].simulcast_idx);
- if (configurations_[i].sending && simulcast_idx < frame_types->size() &&
- (*frame_types)[simulcast_idx] == VideoFrameType::kVideoFrameKey) {
- send_key_frame = true;
- break;
- }
- }
- }
- RTC_DCHECK_EQ(configurations_[0].width, frame_buffer->width());
- RTC_DCHECK_EQ(configurations_[0].height, frame_buffer->height());
-
- for (size_t i = 0; i < encoders_.size(); ++i) {
-
- pictures_[i] = {0};
- pictures_[i].iPicWidth = configurations_[i].width;
- pictures_[i].iPicHeight = configurations_[i].height;
- pictures_[i].iColorFormat = EVideoFormatType::videoFormatI420;
- pictures_[i].uiTimeStamp = input_frame.ntp_time_ms();
-
- if (i == 0) {
- pictures_[i].iStride[0] = frame_buffer->StrideY();
- pictures_[i].iStride[1] = frame_buffer->StrideU();
- pictures_[i].iStride[2] = frame_buffer->StrideV();
- pictures_[i].pData[0] = const_cast<uint8_t*>(frame_buffer->DataY());
- pictures_[i].pData[1] = const_cast<uint8_t*>(frame_buffer->DataU());
- pictures_[i].pData[2] = const_cast<uint8_t*>(frame_buffer->DataV());
- } else {
- pictures_[i].iStride[0] = downscaled_buffers_[i - 1]->StrideY();
- pictures_[i].iStride[1] = downscaled_buffers_[i - 1]->StrideU();
- pictures_[i].iStride[2] = downscaled_buffers_[i - 1]->StrideV();
- pictures_[i].pData[0] =
- const_cast<uint8_t*>(downscaled_buffers_[i - 1]->DataY());
- pictures_[i].pData[1] =
- const_cast<uint8_t*>(downscaled_buffers_[i - 1]->DataU());
- pictures_[i].pData[2] =
- const_cast<uint8_t*>(downscaled_buffers_[i - 1]->DataV());
-
- libyuv::I420Scale(pictures_[i - 1].pData[0], pictures_[i - 1].iStride[0],
- pictures_[i - 1].pData[1], pictures_[i - 1].iStride[1],
- pictures_[i - 1].pData[2], pictures_[i - 1].iStride[2],
- configurations_[i - 1].width,
- configurations_[i - 1].height, pictures_[i].pData[0],
- pictures_[i].iStride[0], pictures_[i].pData[1],
- pictures_[i].iStride[1], pictures_[i].pData[2],
- pictures_[i].iStride[2], configurations_[i].width,
- configurations_[i].height, libyuv::kFilterBilinear);
- }
- if (!configurations_[i].sending) {
- continue;
- }
- if (frame_types != nullptr) {
-
- if ((*frame_types)[i] == VideoFrameType::kEmptyFrame) {
- continue;
- }
- }
- if (send_key_frame) {
-
-
-
- encoders_[i]->ForceIntraFrame(true);
- configurations_[i].key_frame_request = false;
- }
-
- SFrameBSInfo info;
- memset(&info, 0, sizeof(SFrameBSInfo));
-
- int enc_ret = encoders_[i]->EncodeFrame(&pictures_[i], &info);
- if (enc_ret != 0) {
- RTC_LOG(LS_ERROR)
- << "OpenH264 frame encoding failed, EncodeFrame returned " << enc_ret
- << ".";
- ReportError();
- return WEBRTC_VIDEO_CODEC_ERROR;
- }
- encoded_images_[i]._encodedWidth = configurations_[i].width;
- encoded_images_[i]._encodedHeight = configurations_[i].height;
- encoded_images_[i].SetTimestamp(input_frame.timestamp());
- encoded_images_[i]._frameType = ConvertToVideoFrameType(info.eFrameType);
- encoded_images_[i].SetSpatialIndex(configurations_[i].simulcast_idx);
-
-
-
- RtpFragmentize(&encoded_images_[i], &info);
-
-
- if (encoded_images_[i].size() > 0) {
-
- h264_bitstream_parser_.ParseBitstream(encoded_images_[i].data(),
- encoded_images_[i].size());
- h264_bitstream_parser_.GetLastSliceQp(&encoded_images_[i].qp_);
-
- CodecSpecificInfo codec_specific;
- codec_specific.codecType = kVideoCodecH264;
- codec_specific.codecSpecific.H264.packetization_mode =
- packetization_mode_;
- codec_specific.codecSpecific.H264.temporal_idx = kNoTemporalIdx;
- codec_specific.codecSpecific.H264.idr_frame =
- info.eFrameType == videoFrameTypeIDR;
- codec_specific.codecSpecific.H264.base_layer_sync = false;
- if (configurations_[i].num_temporal_layers > 1) {
- const uint8_t tid = info.sLayerInfo[0].uiTemporalId;
- codec_specific.codecSpecific.H264.temporal_idx = tid;
- codec_specific.codecSpecific.H264.base_layer_sync =
- tid > 0 && tid < tl0sync_limit_[i];
- if (codec_specific.codecSpecific.H264.base_layer_sync) {
- tl0sync_limit_[i] = tid;
- }
- if (tid == 0) {
- tl0sync_limit_[i] = configurations_[i].num_temporal_layers;
- }
- }
- encoded_image_callback_->OnEncodedImage(encoded_images_[i],
- &codec_specific);
- }
- }
- return WEBRTC_VIDEO_CODEC_OK;
- }
- SEncParamExt H264EncoderImpl::CreateEncoderParams(size_t i) const {
- SEncParamExt encoder_params;
- encoders_[i]->GetDefaultParams(&encoder_params);
- if (codec_.mode == VideoCodecMode::kRealtimeVideo) {
- encoder_params.iUsageType = CAMERA_VIDEO_REAL_TIME;
- } else if (codec_.mode == VideoCodecMode::kScreensharing) {
- encoder_params.iUsageType = SCREEN_CONTENT_REAL_TIME;
- } else {
- RTC_NOTREACHED();
- }
- encoder_params.iPicWidth = configurations_[i].width;
- encoder_params.iPicHeight = configurations_[i].height;
- encoder_params.iTargetBitrate = configurations_[i].target_bps;
-
-
- encoder_params.iMaxBitrate = UNSPECIFIED_BIT_RATE;
-
- encoder_params.iRCMode = RC_BITRATE_MODE;
- encoder_params.fMaxFrameRate = configurations_[i].max_frame_rate;
-
-
- encoder_params.bEnableFrameSkip = configurations_[i].frame_dropping_on;
-
-
- encoder_params.uiIntraPeriod = configurations_[i].key_frame_interval;
- encoder_params.uiMaxNalSize = 0;
-
-
-
-
- encoder_params.iMultipleThreadIdc = NumberOfThreads(
- encoder_params.iPicWidth, encoder_params.iPicHeight, number_of_cores_);
-
- encoder_params.sSpatialLayers[0].iVideoWidth = encoder_params.iPicWidth;
- encoder_params.sSpatialLayers[0].iVideoHeight = encoder_params.iPicHeight;
- encoder_params.sSpatialLayers[0].fFrameRate = encoder_params.fMaxFrameRate;
- encoder_params.sSpatialLayers[0].iSpatialBitrate =
- encoder_params.iTargetBitrate;
- encoder_params.sSpatialLayers[0].iMaxSpatialBitrate =
- encoder_params.iMaxBitrate;
- encoder_params.iTemporalLayerNum = configurations_[i].num_temporal_layers;
- if (encoder_params.iTemporalLayerNum > 1) {
- encoder_params.iNumRefFrame = 1;
- }
- RTC_LOG(INFO) << "OpenH264 version is " << OPENH264_MAJOR << "."
- << OPENH264_MINOR;
- switch (packetization_mode_) {
- case H264PacketizationMode::SingleNalUnit:
- printf("H264PacketizationMode::SingleNalUnit");
-
- encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
- encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
- SM_SIZELIMITED_SLICE;
- encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceSizeConstraint =
- static_cast<unsigned int>(max_payload_size_);
- RTC_LOG(INFO) << "Encoder is configured with NALU constraint: "
- << max_payload_size_ << " bytes";
- break;
- case H264PacketizationMode::NonInterleaved:
- printf("H264PacketizationMode::NonInterleaved");
-
-
-
-
- encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceNum = 1;
- encoder_params.sSpatialLayers[0].sSliceArgument.uiSliceMode =
- SM_FIXEDSLCNUM_SLICE;
- break;
- }
- return encoder_params;
- }
- void H264EncoderImpl::ReportInit() {
- if (has_reported_init_)
- return;
- RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event",
- kH264EncoderEventInit, kH264EncoderEventMax);
- has_reported_init_ = true;
- }
- void H264EncoderImpl::ReportError() {
- if (has_reported_error_)
- return;
- RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.H264EncoderImpl.Event",
- kH264EncoderEventError, kH264EncoderEventMax);
- has_reported_error_ = true;
- }
- VideoEncoder::EncoderInfo H264EncoderImpl::GetEncoderInfo() const {
- EncoderInfo info;
- info.supports_native_handle = false;
- info.implementation_name = "OpenH264";
- info.scaling_settings =
- VideoEncoder::ScalingSettings(kLowH264QpThreshold, kHighH264QpThreshold);
- info.is_hardware_accelerated = false;
- info.has_internal_source = false;
- info.supports_simulcast = true;
- return info;
- }
- void H264EncoderImpl::LayerConfig::SetStreamState(bool send_stream) {
- if (send_stream && !sending) {
-
- key_frame_request = true;
- }
- sending = send_stream;
- }
- }
|