// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/remoting/proto_utils.h" #include #include "base/big_endian.h" #include "base/logging.h" #include "base/time/time.h" #include "base/values.h" #include "media/base/encryption_scheme.h" #include "media/remoting/proto_enum_utils.h" namespace media { namespace remoting { namespace { constexpr size_t kPayloadVersionFieldSize = sizeof(uint8_t); constexpr size_t kProtoBufferHeaderSize = sizeof(uint16_t); constexpr size_t kDataBufferHeaderSize = sizeof(uint32_t); std::unique_ptr ConvertProtoToDecryptConfig( const pb::DecryptConfig& config_message) { if (!config_message.has_key_id()) return nullptr; if (!config_message.has_iv()) return nullptr; std::vector entries(config_message.sub_samples_size()); for (int i = 0; i < config_message.sub_samples_size(); ++i) { entries.push_back( SubsampleEntry(config_message.sub_samples(i).clear_bytes(), config_message.sub_samples(i).cypher_bytes())); } std::unique_ptr decrypt_config( new DecryptConfig(config_message.key_id(), config_message.iv(), entries)); return decrypt_config; } scoped_refptr ConvertProtoToDecoderBuffer( const pb::DecoderBuffer& buffer_message, scoped_refptr buffer) { if (buffer_message.is_eos()) { VLOG(1) << "EOS data"; return DecoderBuffer::CreateEOSBuffer(); } if (buffer_message.has_timestamp_usec()) { buffer->set_timestamp( base::TimeDelta::FromMicroseconds(buffer_message.timestamp_usec())); } if (buffer_message.has_duration_usec()) { buffer->set_duration( base::TimeDelta::FromMicroseconds(buffer_message.duration_usec())); } VLOG(3) << "timestamp:" << buffer_message.timestamp_usec() << " duration:" << buffer_message.duration_usec(); if (buffer_message.has_is_key_frame()) buffer->set_is_key_frame(buffer_message.is_key_frame()); if (buffer_message.has_decrypt_config()) { buffer->set_decrypt_config( ConvertProtoToDecryptConfig(buffer_message.decrypt_config())); } bool has_discard = false; base::TimeDelta front_discard; if (buffer_message.has_front_discard_usec()) { has_discard = true; front_discard = base::TimeDelta::FromMicroseconds(buffer_message.front_discard_usec()); } base::TimeDelta back_discard; if (buffer_message.has_back_discard_usec()) { has_discard = true; back_discard = base::TimeDelta::FromMicroseconds(buffer_message.back_discard_usec()); } if (has_discard) { buffer->set_discard_padding( DecoderBuffer::DiscardPadding(front_discard, back_discard)); } if (buffer_message.has_splice_timestamp_usec()) { buffer->set_splice_timestamp(base::TimeDelta::FromMicroseconds( buffer_message.splice_timestamp_usec())); } if (buffer_message.has_side_data()) { buffer->CopySideDataFrom( reinterpret_cast(buffer_message.side_data().data()), buffer_message.side_data().size()); } return buffer; } void ConvertDecryptConfigToProto(const DecryptConfig& decrypt_config, pb::DecryptConfig* config_message) { DCHECK(config_message); config_message->set_key_id(decrypt_config.key_id()); config_message->set_iv(decrypt_config.iv()); for (const auto& entry : decrypt_config.subsamples()) { pb::DecryptConfig::SubSample* sub_sample = config_message->add_sub_samples(); sub_sample->set_clear_bytes(entry.clear_bytes); sub_sample->set_cypher_bytes(entry.cypher_bytes); } } void ConvertDecoderBufferToProto(const DecoderBuffer& decoder_buffer, pb::DecoderBuffer* buffer_message) { if (decoder_buffer.end_of_stream()) { buffer_message->set_is_eos(true); return; } VLOG(3) << "timestamp:" << decoder_buffer.timestamp().InMicroseconds() << " duration:" << decoder_buffer.duration().InMicroseconds(); buffer_message->set_timestamp_usec( decoder_buffer.timestamp().InMicroseconds()); buffer_message->set_duration_usec(decoder_buffer.duration().InMicroseconds()); buffer_message->set_is_key_frame(decoder_buffer.is_key_frame()); if (decoder_buffer.decrypt_config()) { ConvertDecryptConfigToProto(*decoder_buffer.decrypt_config(), buffer_message->mutable_decrypt_config()); } buffer_message->set_front_discard_usec( decoder_buffer.discard_padding().first.InMicroseconds()); buffer_message->set_back_discard_usec( decoder_buffer.discard_padding().second.InMicroseconds()); buffer_message->set_splice_timestamp_usec( decoder_buffer.splice_timestamp().InMicroseconds()); if (decoder_buffer.side_data_size()) { buffer_message->set_side_data(decoder_buffer.side_data(), decoder_buffer.side_data_size()); } } } // namespace scoped_refptr ByteArrayToDecoderBuffer(const uint8_t* data, uint32_t size) { base::BigEndianReader reader(reinterpret_cast(data), size); uint8_t payload_version = 0; uint16_t proto_size = 0; pb::DecoderBuffer segment; uint32_t buffer_size = 0; if (reader.ReadU8(&payload_version) && payload_version == 0 && reader.ReadU16(&proto_size) && static_cast(proto_size) < reader.remaining() && segment.ParseFromArray(reader.ptr(), proto_size) && reader.Skip(proto_size) && reader.ReadU32(&buffer_size) && static_cast(buffer_size) <= reader.remaining()) { // Deserialize proto buffer. It passes the pre allocated DecoderBuffer into // the function because the proto buffer may overwrite DecoderBuffer since // it may be EOS buffer. scoped_refptr decoder_buffer = ConvertProtoToDecoderBuffer( segment, DecoderBuffer::CopyFrom(reinterpret_cast(reader.ptr()), buffer_size)); return decoder_buffer; } return nullptr; } std::vector DecoderBufferToByteArray( const DecoderBuffer& decoder_buffer) { pb::DecoderBuffer decoder_buffer_message; ConvertDecoderBufferToProto(decoder_buffer, &decoder_buffer_message); size_t decoder_buffer_size = decoder_buffer.end_of_stream() ? 0 : decoder_buffer.data_size(); size_t size = kPayloadVersionFieldSize + kProtoBufferHeaderSize + decoder_buffer_message.ByteSize() + kDataBufferHeaderSize + decoder_buffer_size; std::vector buffer(size); base::BigEndianWriter writer(reinterpret_cast(buffer.data()), buffer.size()); if (writer.WriteU8(0) && writer.WriteU16( static_cast(decoder_buffer_message.GetCachedSize())) && decoder_buffer_message.SerializeToArray( writer.ptr(), decoder_buffer_message.GetCachedSize()) && writer.Skip(decoder_buffer_message.GetCachedSize()) && writer.WriteU32(decoder_buffer_size)) { if (decoder_buffer_size) { // DecoderBuffer frame data. writer.WriteBytes(reinterpret_cast(decoder_buffer.data()), decoder_buffer.data_size()); } return buffer; } NOTREACHED(); // Reset buffer since serialization of the data failed. buffer.clear(); return buffer; } void ConvertEncryptionSchemeToProto(const EncryptionScheme& encryption_scheme, pb::EncryptionScheme* message) { DCHECK(message); message->set_mode( ToProtoEncryptionSchemeCipherMode(encryption_scheme.mode()).value()); message->set_encrypt_blocks(encryption_scheme.pattern().encrypt_blocks()); message->set_skip_blocks(encryption_scheme.pattern().skip_blocks()); } EncryptionScheme ConvertProtoToEncryptionScheme( const pb::EncryptionScheme& message) { return EncryptionScheme( ToMediaEncryptionSchemeCipherMode(message.mode()).value(), EncryptionScheme::Pattern(message.encrypt_blocks(), message.skip_blocks())); } void ConvertAudioDecoderConfigToProto(const AudioDecoderConfig& audio_config, pb::AudioDecoderConfig* audio_message) { DCHECK(audio_config.IsValidConfig()); DCHECK(audio_message); audio_message->set_codec( ToProtoAudioDecoderConfigCodec(audio_config.codec()).value()); audio_message->set_sample_format( ToProtoAudioDecoderConfigSampleFormat(audio_config.sample_format()) .value()); audio_message->set_channel_layout( ToProtoAudioDecoderConfigChannelLayout(audio_config.channel_layout()) .value()); audio_message->set_samples_per_second(audio_config.samples_per_second()); audio_message->set_seek_preroll_usec( audio_config.seek_preroll().InMicroseconds()); audio_message->set_codec_delay(audio_config.codec_delay()); if (!audio_config.extra_data().empty()) { audio_message->set_extra_data(audio_config.extra_data().data(), audio_config.extra_data().size()); } if (audio_config.is_encrypted()) { pb::EncryptionScheme* encryption_scheme_message = audio_message->mutable_encryption_scheme(); ConvertEncryptionSchemeToProto(audio_config.encryption_scheme(), encryption_scheme_message); } } bool ConvertProtoToAudioDecoderConfig( const pb::AudioDecoderConfig& audio_message, AudioDecoderConfig* audio_config) { DCHECK(audio_config); audio_config->Initialize( ToMediaAudioCodec(audio_message.codec()).value(), ToMediaSampleFormat(audio_message.sample_format()).value(), ToMediaChannelLayout(audio_message.channel_layout()).value(), audio_message.samples_per_second(), std::vector(audio_message.extra_data().begin(), audio_message.extra_data().end()), ConvertProtoToEncryptionScheme(audio_message.encryption_scheme()), base::TimeDelta::FromMicroseconds(audio_message.seek_preroll_usec()), audio_message.codec_delay()); return audio_config->IsValidConfig(); } void ConvertVideoDecoderConfigToProto(const VideoDecoderConfig& video_config, pb::VideoDecoderConfig* video_message) { DCHECK(video_config.IsValidConfig()); DCHECK(video_message); video_message->set_codec( ToProtoVideoDecoderConfigCodec(video_config.codec()).value()); video_message->set_profile( ToProtoVideoDecoderConfigProfile(video_config.profile()).value()); video_message->set_format( ToProtoVideoDecoderConfigFormat(video_config.format()).value()); video_message->set_color_space( ToProtoVideoDecoderConfigColorSpace(video_config.color_space()).value()); pb::Size* coded_size_message = video_message->mutable_coded_size(); coded_size_message->set_width(video_config.coded_size().width()); coded_size_message->set_height(video_config.coded_size().height()); pb::Rect* visible_rect_message = video_message->mutable_visible_rect(); visible_rect_message->set_x(video_config.visible_rect().x()); visible_rect_message->set_y(video_config.visible_rect().y()); visible_rect_message->set_width(video_config.visible_rect().width()); visible_rect_message->set_height(video_config.visible_rect().height()); pb::Size* natural_size_message = video_message->mutable_natural_size(); natural_size_message->set_width(video_config.natural_size().width()); natural_size_message->set_height(video_config.natural_size().height()); if (!video_config.extra_data().empty()) { video_message->set_extra_data(video_config.extra_data().data(), video_config.extra_data().size()); } if (video_config.is_encrypted()) { pb::EncryptionScheme* encryption_scheme_message = video_message->mutable_encryption_scheme(); ConvertEncryptionSchemeToProto(video_config.encryption_scheme(), encryption_scheme_message); } } bool ConvertProtoToVideoDecoderConfig( const pb::VideoDecoderConfig& video_message, VideoDecoderConfig* video_config) { DCHECK(video_config); EncryptionScheme encryption_scheme; video_config->Initialize( ToMediaVideoCodec(video_message.codec()).value(), ToMediaVideoCodecProfile(video_message.profile()).value(), ToMediaVideoPixelFormat(video_message.format()).value(), ToMediaColorSpace(video_message.color_space()).value(), gfx::Size(video_message.coded_size().width(), video_message.coded_size().height()), gfx::Rect(video_message.visible_rect().x(), video_message.visible_rect().y(), video_message.visible_rect().width(), video_message.visible_rect().height()), gfx::Size(video_message.natural_size().width(), video_message.natural_size().height()), std::vector(video_message.extra_data().begin(), video_message.extra_data().end()), ConvertProtoToEncryptionScheme(video_message.encryption_scheme())); return video_config->IsValidConfig(); } void ConvertProtoToPipelineStatistics( const pb::PipelineStatistics& stats_message, PipelineStatistics* stats) { stats->audio_bytes_decoded = stats_message.audio_bytes_decoded(); stats->video_bytes_decoded = stats_message.video_bytes_decoded(); stats->video_frames_decoded = stats_message.video_frames_decoded(); stats->video_frames_dropped = stats_message.video_frames_dropped(); stats->audio_memory_usage = stats_message.audio_memory_usage(); stats->video_memory_usage = stats_message.video_memory_usage(); // HACK: Set the following to prevent "disable video when hidden" logic in // media::blink::WebMediaPlayerImpl. stats->video_keyframe_distance_average = base::TimeDelta::Max(); } void ConvertCdmKeyInfoToProto( const CdmKeysInfo& keys_information, pb::CdmClientOnSessionKeysChange* key_change_message) { for (auto* info : keys_information) { pb::CdmKeyInformation* key = key_change_message->add_key_information(); key->set_key_id(info->key_id.data(), info->key_id.size()); key->set_status(ToProtoCdmKeyInformation(info->status).value()); key->set_system_code(info->system_code); } } void ConvertProtoToCdmKeyInfo( const pb::CdmClientOnSessionKeysChange keychange_message, CdmKeysInfo* key_information) { DCHECK(key_information); key_information->reserve(keychange_message.key_information_size()); for (int i = 0; i < keychange_message.key_information_size(); ++i) { const pb::CdmKeyInformation key_info_msg = keychange_message.key_information(i); std::unique_ptr key(new CdmKeyInformation( key_info_msg.key_id(), ToMediaCdmKeyInformationKeyStatus(key_info_msg.status()).value(), key_info_msg.system_code())); key_information->push_back(std::move(key)); } } void ConvertCdmPromiseToProto(const CdmPromiseResult& result, pb::CdmPromise* promise_message) { promise_message->set_success(result.success()); if (!result.success()) { promise_message->set_exception( ToProtoCdmException(result.exception()).value()); promise_message->set_system_code(result.system_code()); promise_message->set_error_message(result.error_message()); } } void ConvertCdmPromiseWithSessionIdToProto(const CdmPromiseResult& result, const std::string& session_id, pb::CdmPromise* promise_message) { ConvertCdmPromiseToProto(result, promise_message); promise_message->set_session_id(session_id); } void ConvertCdmPromiseWithCdmIdToProto(const CdmPromiseResult& result, int cdm_id, pb::CdmPromise* promise_message) { ConvertCdmPromiseToProto(result, promise_message); promise_message->set_cdm_id(cdm_id); } bool ConvertProtoToCdmPromise(const pb::CdmPromise& promise_message, CdmPromiseResult* result) { if (!promise_message.has_success()) return false; bool success = promise_message.success(); if (success) { *result = CdmPromiseResult::SuccessResult(); return true; } CdmPromise::Exception exception = CdmPromise::UNKNOWN_ERROR; uint32_t system_code = 0; std::string error_message; exception = ToCdmPromiseException(promise_message.exception()).value(); system_code = promise_message.system_code(); error_message = promise_message.error_message(); *result = CdmPromiseResult(exception, system_code, error_message); return true; } bool ConvertProtoToCdmPromiseWithCdmIdSessionId(const pb::RpcMessage& message, CdmPromiseResult* result, int* cdm_id, std::string* session_id) { if (!message.has_cdm_promise_rpc()) return false; const auto& promise_message = message.cdm_promise_rpc(); if (!ConvertProtoToCdmPromise(promise_message, result)) return false; if (cdm_id) *cdm_id = promise_message.cdm_id(); if (session_id) *session_id = promise_message.session_id(); return true; } //============================================================================== CdmPromiseResult::CdmPromiseResult() : CdmPromiseResult(CdmPromise::UNKNOWN_ERROR, 0, "") {} CdmPromiseResult::CdmPromiseResult(CdmPromise::Exception exception, uint32_t system_code, std::string error_message) : success_(false), exception_(exception), system_code_(system_code), error_message_(error_message) {} CdmPromiseResult::CdmPromiseResult(const CdmPromiseResult& other) = default; CdmPromiseResult::~CdmPromiseResult() = default; CdmPromiseResult CdmPromiseResult::SuccessResult() { CdmPromiseResult result(static_cast(0), 0, ""); result.success_ = true; return result; } } // namespace remoting } // namespace media