Shaka Packager SDK
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends
encryption_handler.cc
1 // Copyright 2017 Google Inc. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include "packager/media/crypto/encryption_handler.h"
8 
9 #include <stddef.h>
10 #include <stdint.h>
11 
12 #include <limits>
13 
14 #include "packager/media/base/aes_encryptor.h"
15 #include "packager/media/base/aes_pattern_cryptor.h"
16 #include "packager/media/base/key_source.h"
17 #include "packager/media/base/media_sample.h"
18 #include "packager/media/base/audio_stream_info.h"
19 #include "packager/media/base/video_stream_info.h"
20 #include "packager/media/codecs/video_slice_header_parser.h"
21 #include "packager/media/codecs/vp8_parser.h"
22 #include "packager/media/codecs/vp9_parser.h"
23 
24 namespace shaka {
25 namespace media {
26 
27 namespace {
28 const size_t kCencBlockSize = 16u;
29 
30 // The encryption handler only supports a single output.
31 const size_t kStreamIndex = 0;
32 
33 // The default KID for key rotation is all 0s.
34 const uint8_t kKeyRotationDefaultKeyId[] = {
35  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36 };
37 
38 // Adds one or more subsamples to |*decrypt_config|. This may add more than one
39 // if one of the values overflows the integer in the subsample.
40 void AddSubsample(uint64_t clear_bytes,
41  uint64_t cipher_bytes,
42  DecryptConfig* decrypt_config) {
43  CHECK_LT(cipher_bytes, std::numeric_limits<uint32_t>::max());
44  const uint64_t kUInt16Max = std::numeric_limits<uint16_t>::max();
45  while (clear_bytes > kUInt16Max) {
46  decrypt_config->AddSubsample(kUInt16Max, 0);
47  clear_bytes -= kUInt16Max;
48  }
49 
50  if (clear_bytes > 0 || cipher_bytes > 0)
51  decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
52 }
53 
54 uint8_t GetNaluLengthSize(const StreamInfo& stream_info) {
55  if (stream_info.stream_type() != kStreamVideo)
56  return 0;
57 
58  const VideoStreamInfo& video_stream_info =
59  static_cast<const VideoStreamInfo&>(stream_info);
60  return video_stream_info.nalu_length_size();
61 }
62 
63 std::string GetStreamLabelForEncryption(
64  const StreamInfo& stream_info,
65  const std::function<std::string(
66  const EncryptionParams::EncryptedStreamAttributes& stream_attributes)>&
67  stream_label_func) {
68  EncryptionParams::EncryptedStreamAttributes stream_attributes;
69  if (stream_info.stream_type() == kStreamAudio) {
70  stream_attributes.stream_type =
71  EncryptionParams::EncryptedStreamAttributes::kAudio;
72  } else if (stream_info.stream_type() == kStreamVideo) {
73  const VideoStreamInfo& video_stream_info =
74  static_cast<const VideoStreamInfo&>(stream_info);
75  stream_attributes.stream_type =
76  EncryptionParams::EncryptedStreamAttributes::kVideo;
77  stream_attributes.oneof.video.width = video_stream_info.width();
78  stream_attributes.oneof.video.height = video_stream_info.height();
79  }
80  return stream_label_func(stream_attributes);
81 }
82 } // namespace
83 
84 EncryptionHandler::EncryptionHandler(const EncryptionParams& encryption_params,
85  KeySource* key_source)
86  : encryption_params_(encryption_params),
87  protection_scheme_(
88  static_cast<FourCC>(encryption_params.protection_scheme)),
89  key_source_(key_source) {}
90 
91 EncryptionHandler::~EncryptionHandler() {}
92 
94  if (!encryption_params_.stream_label_func) {
95  return Status(error::INVALID_ARGUMENT, "Stream label function not set.");
96  }
97  if (num_input_streams() != 1 || next_output_stream_index() != 1) {
98  return Status(error::INVALID_ARGUMENT,
99  "Expects exactly one input and output.");
100  }
101  return Status::OK;
102 }
103 
104 Status EncryptionHandler::Process(std::unique_ptr<StreamData> stream_data) {
105  switch (stream_data->stream_data_type) {
106  case StreamDataType::kStreamInfo:
107  return ProcessStreamInfo(*stream_data->stream_info);
108  case StreamDataType::kSegmentInfo: {
109  std::shared_ptr<SegmentInfo> segment_info(new SegmentInfo(
110  *stream_data->segment_info));
111 
112  segment_info->is_encrypted = remaining_clear_lead_ <= 0;
113 
114  const bool key_rotation_enabled = crypto_period_duration_ != 0;
115  if (key_rotation_enabled)
116  segment_info->key_rotation_encryption_config = encryption_config_;
117  if (!segment_info->is_subsegment) {
118  if (key_rotation_enabled)
119  check_new_crypto_period_ = true;
120  if (remaining_clear_lead_ > 0)
121  remaining_clear_lead_ -= segment_info->duration;
122  }
123 
124  return DispatchSegmentInfo(kStreamIndex, segment_info);
125  }
126  case StreamDataType::kMediaSample:
127  return ProcessMediaSample(std::move(stream_data->media_sample));
128  default:
129  VLOG(3) << "Stream data type "
130  << static_cast<int>(stream_data->stream_data_type) << " ignored.";
131  return Dispatch(std::move(stream_data));
132  }
133 }
134 
135 Status EncryptionHandler::ProcessStreamInfo(const StreamInfo& clear_info) {
136  if (clear_info.is_encrypted()) {
137  return Status(error::INVALID_ARGUMENT,
138  "Input stream is already encrypted.");
139  }
140 
141  DCHECK_NE(kStreamUnknown, clear_info.stream_type());
142  DCHECK_NE(kStreamText, clear_info.stream_type());
143  std::shared_ptr<StreamInfo> stream_info = clear_info.Clone();
144 
145  remaining_clear_lead_ =
146  encryption_params_.clear_lead_in_seconds * stream_info->time_scale();
147  crypto_period_duration_ =
148  encryption_params_.crypto_period_duration_in_seconds *
149  stream_info->time_scale();
150  codec_ = stream_info->codec();
151  nalu_length_size_ = GetNaluLengthSize(*stream_info);
152  stream_label_ = GetStreamLabelForEncryption(
153  *stream_info, encryption_params_.stream_label_func);
154  switch (codec_) {
155  case kCodecVP9:
156  if (encryption_params_.vp9_subsample_encryption)
157  vpx_parser_.reset(new VP9Parser);
158  break;
159  case kCodecH264:
160  header_parser_.reset(new H264VideoSliceHeaderParser);
161  break;
162  case kCodecH265:
163  header_parser_.reset(new H265VideoSliceHeaderParser);
164  break;
165  default:
166  // Other codecs should have nalu length size == 0.
167  if (nalu_length_size_ > 0) {
168  LOG(WARNING) << "Unknown video codec '" << codec_ << "'";
169  return Status(error::ENCRYPTION_FAILURE, "Unknown video codec.");
170  }
171  }
172  if (header_parser_) {
173  CHECK_NE(nalu_length_size_, 0u) << "AnnexB stream is not supported yet";
174  if (!header_parser_->Initialize(stream_info->codec_config())) {
175  return Status(error::ENCRYPTION_FAILURE,
176  "Fail to read SPS and PPS data.");
177  }
178  }
179 
180  Status status = SetupProtectionPattern(stream_info->stream_type());
181  if (!status.ok())
182  return status;
183 
184  EncryptionKey encryption_key;
185  const bool key_rotation_enabled = crypto_period_duration_ != 0;
186  if (key_rotation_enabled) {
187  check_new_crypto_period_ = true;
188  // Setup dummy key id and key to signal encryption for key rotation.
189  encryption_key.key_id.assign(
190  kKeyRotationDefaultKeyId,
191  kKeyRotationDefaultKeyId + sizeof(kKeyRotationDefaultKeyId));
192  // The key is not really used to encrypt any data. It is there just for
193  // convenience.
194  encryption_key.key = encryption_key.key_id;
195  } else {
196  status = key_source_->GetKey(stream_label_, &encryption_key);
197  if (!status.ok())
198  return status;
199  }
200  if (!CreateEncryptor(encryption_key))
201  return Status(error::ENCRYPTION_FAILURE, "Failed to create encryptor");
202 
203  stream_info->set_is_encrypted(true);
204  stream_info->set_has_clear_lead(encryption_params_.clear_lead_in_seconds > 0);
205  stream_info->set_encryption_config(*encryption_config_);
206 
207  return DispatchStreamInfo(kStreamIndex, stream_info);
208 }
209 
210 Status EncryptionHandler::ProcessMediaSample(
211  std::shared_ptr<const MediaSample> clear_sample) {
212  DCHECK(clear_sample);
213 
214  // We need to parse the frame (which also updates the vpx parser) even if the
215  // frame is not encrypted as the next (encrypted) frame may be dependent on
216  // this clear frame.
217  std::vector<VPxFrameInfo> vpx_frames;
218  if (vpx_parser_ && !vpx_parser_->Parse(clear_sample->data(),
219  clear_sample->data_size(),
220  &vpx_frames)) {
221  return Status(error::ENCRYPTION_FAILURE, "Failed to parse vpx frame.");
222  }
223 
224  // Need to setup the encryptor for new segments even if this segment does not
225  // need to be encrypted, so we can signal encryption metadata earlier to
226  // allows clients to prefetch the keys.
227  if (check_new_crypto_period_) {
228  const int64_t current_crypto_period_index =
229  clear_sample->dts() / crypto_period_duration_;
230  if (current_crypto_period_index != prev_crypto_period_index_) {
231  EncryptionKey encryption_key;
232  Status status = key_source_->GetCryptoPeriodKey(
233  current_crypto_period_index, stream_label_, &encryption_key);
234  if (!status.ok())
235  return status;
236  if (!CreateEncryptor(encryption_key))
237  return Status(error::ENCRYPTION_FAILURE, "Failed to create encryptor");
238  prev_crypto_period_index_ = current_crypto_period_index;
239  }
240  check_new_crypto_period_ = false;
241  }
242 
243  // Since there is no encryption needed right now, send the clear copy
244  // downstream so we can save the costs of copying it.
245  if (remaining_clear_lead_ > 0) {
246  return DispatchMediaSample(kStreamIndex, std::move(clear_sample));
247  }
248 
249  std::unique_ptr<DecryptConfig> decrypt_config(new DecryptConfig(
250  encryption_config_->key_id,
251  encryptor_->iv(),
252  std::vector<SubsampleEntry>(),
253  protection_scheme_,
254  crypt_byte_block_,
255  skip_byte_block_));
256 
257  // Now that we know that this sample must be encrypted, make a copy of
258  // the sample first so that all the encryption operations can be done
259  // in-place.
260  std::shared_ptr<MediaSample> cipher_sample =
261  MediaSample::CopyFrom(*clear_sample);
262 
263  Status result;
264  if (vpx_parser_) {
265  if (EncryptVpxFrame(vpx_frames,
266  cipher_sample->writable_data(),
267  cipher_sample->data_size(),
268  decrypt_config.get())) {
269  DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
270  cipher_sample->data_size());
271  } else {
272  result = Status(
273  error::ENCRYPTION_FAILURE,
274  "Failed to encrypt VPX frame.");
275  }
276  } else if (header_parser_) {
277  if (EncryptNalFrame(cipher_sample->writable_data(),
278  cipher_sample->data_size(),
279  decrypt_config.get())) {
280  DCHECK_EQ(decrypt_config->GetTotalSizeOfSubsamples(),
281  cipher_sample->data_size());
282  } else {
283  result = Status(
284  error::ENCRYPTION_FAILURE,
285  "Failed to encrypt NAL frame.");
286  }
287  } else if (cipher_sample->data_size() > leading_clear_bytes_size_) {
288  EncryptBytes(
289  cipher_sample->writable_data() + leading_clear_bytes_size_,
290  cipher_sample->data_size() - leading_clear_bytes_size_);
291  }
292 
293  if (!result.ok()) {
294  return result;
295  }
296 
297  encryptor_->UpdateIv();
298 
299  // Finish initializing the sample before sending it downstream. We must
300  // wait until now to finish the initialization as we will loose access to
301  // |decrypt_config| once we set it.
302  cipher_sample->set_is_encrypted(true);
303  cipher_sample->set_decrypt_config(std::move(decrypt_config));
304 
305  return DispatchMediaSample(kStreamIndex, std::move(cipher_sample));
306 }
307 
308 Status EncryptionHandler::SetupProtectionPattern(StreamType stream_type) {
309  switch (protection_scheme_) {
310  case kAppleSampleAesProtectionScheme: {
311  const size_t kH264LeadingClearBytesSize = 32u;
312  const size_t kSmallNalUnitSize = 32u + 16u;
313  const size_t kAudioLeadingClearBytesSize = 16u;
314  switch (codec_) {
315  case kCodecH264:
316  // Apple Sample AES uses 1:9 pattern for video.
317  crypt_byte_block_ = 1u;
318  skip_byte_block_ = 9u;
319  leading_clear_bytes_size_ = kH264LeadingClearBytesSize;
320  min_protected_data_size_ = kSmallNalUnitSize + 1u;
321  break;
322  case kCodecAAC:
323  FALLTHROUGH_INTENDED;
324  case kCodecAC3:
325  // Audio is whole sample encrypted. We could not use a
326  // crypto_byte_block_ of 1 here as if there is one crypto block
327  // remaining, it need not be encrypted for video but it needs to be
328  // encrypted for audio.
329  crypt_byte_block_ = 0u;
330  skip_byte_block_ = 0u;
331  leading_clear_bytes_size_ = kAudioLeadingClearBytesSize;
332  min_protected_data_size_ = leading_clear_bytes_size_ + 1u;
333  break;
334  default:
335  return Status(error::ENCRYPTION_FAILURE,
336  "Only AAC/AC3 and H264 are supported in Sample AES.");
337  }
338  break;
339  }
340  case FOURCC_cbcs:
341  FALLTHROUGH_INTENDED;
342  case FOURCC_cens:
343  if (stream_type == kStreamVideo) {
344  // Use 1:9 pattern for video.
345  crypt_byte_block_ = 1u;
346  skip_byte_block_ = 9u;
347  } else {
348  // Tracks other than video are protected using whole-block full-sample
349  // encryption, which is essentially a pattern of 1:0. Note that this may
350  // not be the same as the non-pattern based encryption counterparts,
351  // e.g. in 'cens' for full sample encryption, the whole sample is
352  // encrypted up to the last 16-byte boundary, see 23001-7:2016(E) 9.7;
353  // while in 'cenc' for full sample encryption, the last partial 16-byte
354  // block is also encrypted, see 23001-7:2016(E) 9.4.2. Another
355  // difference is the use of constant iv.
356  crypt_byte_block_ = 1u;
357  skip_byte_block_ = 0u;
358  }
359  break;
360  default:
361  // Not using pattern encryption.
362  crypt_byte_block_ = 0u;
363  skip_byte_block_ = 0u;
364  }
365  return Status::OK;
366 }
367 
368 bool EncryptionHandler::CreateEncryptor(const EncryptionKey& encryption_key) {
369  std::unique_ptr<AesCryptor> encryptor;
370  switch (protection_scheme_) {
371  case FOURCC_cenc:
372  encryptor.reset(new AesCtrEncryptor);
373  break;
374  case FOURCC_cbc1:
375  encryptor.reset(new AesCbcEncryptor(kNoPadding));
376  break;
377  case FOURCC_cens:
378  encryptor.reset(new AesPatternCryptor(
379  crypt_byte_block_, skip_byte_block_,
381  AesCryptor::kDontUseConstantIv,
382  std::unique_ptr<AesCryptor>(new AesCtrEncryptor())));
383  break;
384  case FOURCC_cbcs:
385  encryptor.reset(new AesPatternCryptor(
386  crypt_byte_block_, skip_byte_block_,
388  AesCryptor::kUseConstantIv,
389  std::unique_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
390  break;
391  case kAppleSampleAesProtectionScheme:
392  if (crypt_byte_block_ == 0 && skip_byte_block_ == 0) {
393  encryptor.reset(
394  new AesCbcEncryptor(kNoPadding, AesCryptor::kUseConstantIv));
395  } else {
396  encryptor.reset(new AesPatternCryptor(
397  crypt_byte_block_, skip_byte_block_,
399  AesCryptor::kUseConstantIv,
400  std::unique_ptr<AesCryptor>(new AesCbcEncryptor(kNoPadding))));
401  }
402  break;
403  default:
404  LOG(ERROR) << "Unsupported protection scheme.";
405  return false;
406  }
407 
408  std::vector<uint8_t> iv = encryption_key.iv;
409  if (iv.empty()) {
410  if (!AesCryptor::GenerateRandomIv(protection_scheme_, &iv)) {
411  LOG(ERROR) << "Failed to generate random iv.";
412  return false;
413  }
414  }
415  const bool initialized =
416  encryptor->InitializeWithIv(encryption_key.key, iv);
417  encryptor_ = std::move(encryptor);
418 
419  encryption_config_.reset(new EncryptionConfig);
420  encryption_config_->protection_scheme = protection_scheme_;
421  encryption_config_->crypt_byte_block = crypt_byte_block_;
422  encryption_config_->skip_byte_block = skip_byte_block_;
423  if (encryptor_->use_constant_iv()) {
424  encryption_config_->per_sample_iv_size = 0;
425  encryption_config_->constant_iv = iv;
426  } else {
427  encryption_config_->per_sample_iv_size = static_cast<uint8_t>(iv.size());
428  }
429  encryption_config_->key_id = encryption_key.key_id;
430  encryption_config_->key_system_info = encryption_key.key_system_info;
431  return initialized;
432 }
433 
434 bool EncryptionHandler::EncryptVpxFrame(
435  const std::vector<VPxFrameInfo>& vpx_frames,
436  uint8_t* source,
437  size_t source_size,
438  DecryptConfig* decrypt_config) {
439  uint8_t* data = source;
440  for (const VPxFrameInfo& frame : vpx_frames) {
441  uint16_t clear_bytes =
442  static_cast<uint16_t>(frame.uncompressed_header_size);
443  uint32_t cipher_bytes = static_cast<uint32_t>(
444  frame.frame_size - frame.uncompressed_header_size);
445 
446  // "VP Codec ISO Media File Format Binding" document requires that the
447  // encrypted bytes of each frame within the superframe must be block
448  // aligned so that the counter state can be computed for each frame
449  // within the superframe.
450  // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
451  // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to
452  // avoid partial blocks in Subsamples.
453  // For consistency, apply block alignment to all frames.
454  const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
455  clear_bytes += misalign_bytes;
456  cipher_bytes -= misalign_bytes;
457 
458  decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
459  if (cipher_bytes > 0)
460  EncryptBytes(data + clear_bytes, cipher_bytes);
461  data += frame.frame_size;
462  }
463  // Add subsample for the superframe index if exists.
464  const bool is_superframe = vpx_frames.size() > 1;
465  if (is_superframe) {
466  size_t index_size = source + source_size - data;
467  DCHECK_LE(index_size, 2 + vpx_frames.size() * 4);
468  DCHECK_GE(index_size, 2 + vpx_frames.size() * 1);
469  uint16_t clear_bytes = static_cast<uint16_t>(index_size);
470  uint32_t cipher_bytes = 0;
471  decrypt_config->AddSubsample(clear_bytes, cipher_bytes);
472  }
473  return true;
474 }
475 
476 bool EncryptionHandler::EncryptNalFrame(uint8_t* data,
477  size_t data_length,
478  DecryptConfig* decrypt_config) {
479  DCHECK_NE(nalu_length_size_, 0u);
480  DCHECK(header_parser_);
481  const Nalu::CodecType nalu_type =
482  (codec_ == kCodecH265) ? Nalu::kH265 : Nalu::kH264;
483  NaluReader reader(nalu_type, nalu_length_size_, data, data_length);
484 
485  // Store the current length of clear data. This is used to squash
486  // multiple unencrypted NAL units into fewer subsample entries.
487  uint64_t accumulated_clear_bytes = 0;
488 
489  Nalu nalu;
490  NaluReader::Result result;
491  while ((result = reader.Advance(&nalu)) == NaluReader::kOk) {
492  const uint64_t nalu_total_size = nalu.header_size() + nalu.payload_size();
493  if (nalu.is_video_slice() && nalu_total_size >= min_protected_data_size_) {
494  uint64_t current_clear_bytes = leading_clear_bytes_size_;
495  if (current_clear_bytes == 0) {
496  // For video-slice NAL units, encrypt the video slice. This skips
497  // the frame header.
498  const int64_t video_slice_header_size =
499  header_parser_->GetHeaderSize(nalu);
500  if (video_slice_header_size < 0) {
501  LOG(ERROR) << "Failed to read slice header.";
502  return false;
503  }
504  current_clear_bytes = nalu.header_size() + video_slice_header_size;
505  }
506  uint64_t cipher_bytes = nalu_total_size - current_clear_bytes;
507 
508  // ISO/IEC 23001-7:2016 10.2 'cbc1' 10.3 'cens'
509  // The BytesOfProtectedData size SHALL be a multiple of 16 bytes to
510  // avoid partial blocks in Subsamples.
511  // CMAF requires 'cenc' scheme BytesOfProtectedData SHALL be a multiple
512  // of 16 bytes; while 'cbcs' scheme BytesOfProtectedData SHALL start on
513  // the first byte of video data following the slice header.
514  if (protection_scheme_ == FOURCC_cbc1 ||
515  protection_scheme_ == FOURCC_cens ||
516  protection_scheme_ == FOURCC_cenc) {
517  const uint16_t misalign_bytes = cipher_bytes % kCencBlockSize;
518  current_clear_bytes += misalign_bytes;
519  cipher_bytes -= misalign_bytes;
520  }
521 
522  const uint8_t* nalu_data = nalu.data() + current_clear_bytes;
523  EncryptBytes(const_cast<uint8_t*>(nalu_data), cipher_bytes);
524 
525  AddSubsample(
526  accumulated_clear_bytes + nalu_length_size_ + current_clear_bytes,
527  cipher_bytes, decrypt_config);
528  accumulated_clear_bytes = 0;
529  } else {
530  // For non-video-slice or small NAL units, don't encrypt.
531  accumulated_clear_bytes += nalu_length_size_ + nalu_total_size;
532  }
533  }
534  if (result != NaluReader::kEOStream) {
535  LOG(ERROR) << "Failed to parse NAL units.";
536  return false;
537  }
538  AddSubsample(accumulated_clear_bytes, 0, decrypt_config);
539  return true;
540 }
541 
542 void EncryptionHandler::EncryptBytes(uint8_t* data, size_t size) {
543  DCHECK(data);
544  DCHECK(encryptor_);
545  CHECK(encryptor_->Crypt(data, size, data));
546 }
547 
548 void EncryptionHandler::InjectVpxParserForTesting(
549  std::unique_ptr<VPxParser> vpx_parser) {
550  vpx_parser_ = std::move(vpx_parser);
551 }
552 
553 void EncryptionHandler::InjectVideoSliceHeaderParserForTesting(
554  std::unique_ptr<VideoSliceHeaderParser> header_parser) {
555  header_parser_ = std::move(header_parser);
556 }
557 
558 } // namespace media
559 } // namespace shaka
Abstract class holds stream information.
Definition: stream_info.h:58
virtual std::unique_ptr< StreamInfo > Clone() const =0
std::function< std::string(const EncryptedStreamAttributes &stream_attributes)> stream_label_func
virtual Status GetCryptoPeriodKey(uint32_t crypto_period_index, const std::string &stream_label, EncryptionKey *key)=0
Status Dispatch(std::unique_ptr< StreamData > stream_data)
virtual Status GetKey(const std::string &stream_label, EncryptionKey *key)=0
double clear_lead_in_seconds
Clear lead duration in seconds.
bool vp9_subsample_encryption
Enable/disable subsample encryption for VP9.
Status Process(std::unique_ptr< StreamData > stream_data) override
static bool GenerateRandomIv(FourCC protection_scheme, std::vector< uint8_t > *iv)
Definition: aes_cryptor.cc:107
Status DispatchStreamInfo(size_t stream_index, std::shared_ptr< const StreamInfo > stream_info)
Dispatch the stream info to downstream handlers.
Status DispatchMediaSample(size_t stream_index, std::shared_ptr< const MediaSample > media_sample)
Dispatch the media sample to downstream handlers.
static std::shared_ptr< MediaSample > CopyFrom(const uint8_t *data, size_t size, bool is_key_frame)
Definition: media_sample.cc:45
Status DispatchSegmentInfo(size_t stream_index, std::shared_ptr< const SegmentInfo > segment_info)
Dispatch the segment info to downstream handlers.