From e4e2dafa52ae737117429ec3dd12d42e3039abd9 Mon Sep 17 00:00:00 2001 From: Ramji Chandramouli Date: Mon, 14 Jul 2014 14:35:57 -0700 Subject: [PATCH] WVM parser and demuxer. Change-Id: Id54f285afd617b00b7bdd077dc37898b0562a8f4 --- media/base/audio_stream_info.h | 5 + media/base/container_names.cc | 2 + media/base/demuxer.cc | 4 + media/base/media_base.gyp | 2 + media/base/media_sample.cc | 6 + media/base/media_sample.h | 19 +- media/base/network_util.cc | 30 + media/base/network_util.h | 19 + media/base/stream_info.cc | 5 +- media/base/stream_info.h | 4 + media/formats/wvm/wvm.gyp | 47 ++ media/formats/wvm/wvm_media_parser.cc | 895 ++++++++++++++++++++++++++ media/formats/wvm/wvm_media_parser.h | 260 ++++++++ packager.gyp | 4 + 14 files changed, 1298 insertions(+), 4 deletions(-) create mode 100644 media/base/network_util.cc create mode 100644 media/base/network_util.h create mode 100644 media/formats/wvm/wvm.gyp create mode 100644 media/formats/wvm/wvm_media_parser.cc create mode 100644 media/formats/wvm/wvm_media_parser.h diff --git a/media/base/audio_stream_info.h b/media/base/audio_stream_info.h index 8b6fb625d1..d8fe21720e 100644 --- a/media/base/audio_stream_info.h +++ b/media/base/audio_stream_info.h @@ -64,6 +64,11 @@ class AudioStreamInfo : public StreamInfo { return static_cast(num_channels_) * sample_bits_ / 8; } + void set_sampling_frequency(const uint32 sampling_frequency) { + sampling_frequency_ = sampling_frequency; + } + + /// @param audio_object_type is only used by AAC Codec, ignored otherwise. /// @return The codec string. static std::string GetCodecString(AudioCodec codec, uint8 audio_object_type); diff --git a/media/base/container_names.cc b/media/base/container_names.cc index 3cad5b6e57..330004f768 100644 --- a/media/base/container_names.cc +++ b/media/base/container_names.cc @@ -1573,6 +1573,8 @@ static MediaContainerName LookupContainerByFirst4(const uint8* buffer, return CONTAINER_WTV; } break; + case 0x000001ba: + return CONTAINER_MPEG2PS; } // Now try a few different ones that look at something other diff --git a/media/base/demuxer.cc b/media/base/demuxer.cc index fe0388e026..f840ded93a 100644 --- a/media/base/demuxer.cc +++ b/media/base/demuxer.cc @@ -18,6 +18,7 @@ #include "media/file/file.h" #include "media/formats/mp2t/mp2t_media_parser.h" #include "media/formats/mp4/mp4_media_parser.h" +#include "media/formats/wvm/wvm_media_parser.h" namespace { const size_t kBufSize = 0x40000; // 256KB. @@ -65,6 +66,9 @@ Status Demuxer::Initialize() { case CONTAINER_MPEG2TS: parser_.reset(new mp2t::Mp2tMediaParser()); break; + case CONTAINER_MPEG2PS: + parser_.reset(new wvm::WvmMediaParser()); + break; default: NOTIMPLEMENTED(); return Status(error::UNIMPLEMENTED, "Container not supported."); diff --git a/media/base/media_base.gyp b/media/base/media_base.gyp index e913d5cc1b..7cd60a5299 100644 --- a/media/base/media_base.gyp +++ b/media/base/media_base.gyp @@ -52,6 +52,8 @@ 'muxer_options.h', 'muxer_util.cc', 'muxer_util.h', + 'network_util.cc', + 'network_util.h', 'offset_byte_queue.cc', 'offset_byte_queue.h', 'producer_consumer_queue.h', diff --git a/media/base/media_sample.cc b/media/base/media_sample.cc index 016d9df9bd..3af8f5b7d9 100644 --- a/media/base/media_sample.cc +++ b/media/base/media_sample.cc @@ -54,6 +54,12 @@ scoped_refptr MediaSample::CopyFrom(const uint8* data, data, data_size, side_data, side_data_size, is_key_frame)); } +// static +scoped_refptr MediaSample::CreateEmptyMediaSample() { + MediaSample* media_sample = new MediaSample(); + return make_scoped_refptr(media_sample); +} + // static scoped_refptr MediaSample::CreateEOSBuffer() { return make_scoped_refptr(new MediaSample(NULL, 0, NULL, 0, false)); diff --git a/media/base/media_sample.h b/media/base/media_sample.h index 060d5dbe2d..d0d36606cd 100644 --- a/media/base/media_sample.h +++ b/media/base/media_sample.h @@ -45,6 +45,9 @@ class MediaSample : public base::RefCountedThreadSafe { size_t side_data_size, bool is_key_frame); + /// Create a MediaSample object with default members. + static scoped_refptr CreateEmptyMediaSample(); + /// Create a MediaSample indicating we've reached end of stream. /// Calling any method other than end_of_stream() on the resulting buffer /// is disallowed. @@ -56,7 +59,6 @@ class MediaSample : public base::RefCountedThreadSafe { } void set_dts(int64 dts) { - DCHECK(!end_of_stream()); dts_ = dts; } @@ -66,7 +68,6 @@ class MediaSample : public base::RefCountedThreadSafe { } void set_pts(int64 pts) { - DCHECK(!end_of_stream()); pts_ = pts; } @@ -110,6 +111,15 @@ class MediaSample : public base::RefCountedThreadSafe { return side_data_.size(); } + void set_data(const uint8* data, const size_t data_size) { + data_size_ = data_size; + data_.assign(data, data + data_size_); + } + + void set_is_key_frame(bool value) { + is_key_frame_ = value; + } + // If there's no data in this buffer, it represents end of stream. bool end_of_stream() const { return data_.size() == 0; } @@ -127,6 +137,10 @@ class MediaSample : public base::RefCountedThreadSafe { const uint8* side_data, size_t side_data_size, bool is_key_frame); + MediaSample() : dts_(0), pts_(0), + duration_(0), + is_key_frame_(false), + data_size_(0) {} virtual ~MediaSample(); // Decoding time stamp. @@ -142,6 +156,7 @@ class MediaSample : public base::RefCountedThreadSafe { // http://www.matroska.org/technical/specs/index.html BlockAdditional[A5]. // Not used by mp4 and other containers. std::vector side_data_; + size_t data_size_; DISALLOW_COPY_AND_ASSIGN(MediaSample); }; diff --git a/media/base/network_util.cc b/media/base/network_util.cc new file mode 100644 index 0000000000..965306425d --- /dev/null +++ b/media/base/network_util.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 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/base/network_util.h" + +namespace media { + +uint32 +ntohlFromBuffer(const unsigned char * buf) { + return (static_cast(buf[0])<<24) | (static_cast(buf[1])<<16) + | (static_cast(buf[2])<<8) | (static_cast(buf[3])); + +} + +uint16 +ntohsFromBuffer( const unsigned char * buf) { + return (static_cast(buf[0])<<8) | (static_cast(buf[1])); +} + +uint64 +ntohllFromBuffer( const unsigned char * buf ) { + return (static_cast(buf[0])<<56)| (static_cast(buf[1])<<48) + | (static_cast(buf[2])<<40)| (static_cast(buf[3])<<32) + | (static_cast(buf[4])<<24)| (static_cast(buf[5])<<16) + | (static_cast(buf[6])<<8) | (static_cast(buf[7])); +} + +} // namespace media + diff --git a/media/base/network_util.h b/media/base/network_util.h new file mode 100644 index 0000000000..87037c3a0d --- /dev/null +++ b/media/base/network_util.h @@ -0,0 +1,19 @@ +// Copyright (c) 2012 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. + +#ifndef MEDIA_BASE_NETWORK_UTIL_H_ +#define MEDIA_BASE_NETWORK_UTIL_H_ + +#include "base/base_export.h" +#include "base/basictypes.h" + +namespace media { + +uint32 ntohlFromBuffer(const unsigned char * buf); +uint16 ntohsFromBuffer(const unsigned char * buf); +uint64 ntohllFromBuffer(const unsigned char * buf); + +} // namespace media + +#endif // MEDIA_BASE_NETWORK_UTIL_H_ diff --git a/media/base/stream_info.cc b/media/base/stream_info.cc index ba149f611e..3b26038bfd 100644 --- a/media/base/stream_info.cc +++ b/media/base/stream_info.cc @@ -30,8 +30,9 @@ StreamInfo::StreamInfo(StreamType stream_type, language_(language), is_encrypted_(is_encrypted) { - CHECK((extra_data_size != 0) == (extra_data != NULL)); - extra_data_.assign(extra_data, extra_data + extra_data_size); + if (extra_data_size > 0) { + extra_data_.assign(extra_data, extra_data + extra_data_size); + } } StreamInfo::~StreamInfo() {} diff --git a/media/base/stream_info.h b/media/base/stream_info.h index e0f81bd4b3..1d6ed69297 100644 --- a/media/base/stream_info.h +++ b/media/base/stream_info.h @@ -55,6 +55,10 @@ class StreamInfo : public base::RefCountedThreadSafe { void set_extra_data(const std::vector& data) { extra_data_ = data; } + void set_codec_string(const std::string& codec_string) { + codec_string_ = codec_string; + } + protected: friend class base::RefCountedThreadSafe; virtual ~StreamInfo(); diff --git a/media/formats/wvm/wvm.gyp b/media/formats/wvm/wvm.gyp new file mode 100644 index 0000000000..e4c4774495 --- /dev/null +++ b/media/formats/wvm/wvm.gyp @@ -0,0 +1,47 @@ +# Copyright 2014 Google Inc. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +{ + 'variables': { + # Compile as chromium code to enable warnings and warnings-as-errors. + 'chromium_code': 1, + }, + 'target_defaults': { + 'include_dirs': [ + '../../..', + ], + }, + 'targets': [ + { + 'target_name': 'wvm', + 'type': '<(component)', + 'sources': [ + 'wvm_media_parser.cc', + 'wvm_media_parser.h', + ], + 'dependencies': [ + '../../base/media_base.gyp:base', + ], + }, +# { +# 'target_name': 'wvm_unittest', +# 'type': '<(gtest_target_type)', +# 'sources': [ +# 'adts_header_unittest.cc', +# 'es_parser_h264_unittest.cc', +# 'wvm_media_parser_unittest.cc', +# ], +# 'dependencies': [ +# '../../../testing/gtest.gyp:gtest', +# '../../../testing/gmock.gyp:gmock', +# '../../filters/filters.gyp:filters', +# '../../test/media_test.gyp:media_test_support', +# '../mpeg/mpeg.gyp:mpeg', +# 'wvm', +# ] +# }, + ], +} diff --git a/media/formats/wvm/wvm_media_parser.cc b/media/formats/wvm/wvm_media_parser.cc new file mode 100644 index 0000000000..81f7ab1388 --- /dev/null +++ b/media/formats/wvm/wvm_media_parser.cc @@ -0,0 +1,895 @@ +// Copyright 2014 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 +#include +#include +#include +#include + +#include "base/strings/string_number_conversions.h" +#include "media/formats/mp2t/adts_header.h" +#include "media/formats/wvm/wvm_media_parser.h" + + +#define HAS_HEADER_EXTENSION(x) ((x != 0xBC) && (x != 0xBE) && (x != 0xBF) \ + && (x != 0xF0) && (x != 0xF2) && (x != 0xF8) \ + && (x != 0xFF)) + +namespace { + const uint32 kMpeg2ClockRate = 90000; + const uint32 kPesOptPts = 0x80; + const uint32 kPesOptDts = 0x40; + const uint32 kPesOptAlign = 0x04; + const uint32 kPsmStreamId = 0xBC; + const uint32 kPaddingStreamId = 0xBE; + const uint32 kIndexMagic = 0x49444d69; + const uint32 kIndexStreamId = 0xBF; // private_stream_2 + const uint32 kIndexVersion4HeaderSize = 12; + const uint32 kEcmStreamId = 0xF0; + const uint32 kV2MetadataStreamId = 0xF1; // EMM_stream + const uint32 kScramblingBitsMask = 0x30; + const uint32 kEncryptedOddKey = 0x30; + const uint32 kStartCode1 = 0x00; + const uint32 kStartCode2 = 0x00; + const uint32 kStartCode3 = 0x01; + const uint32 kStartCode4Pack = 0xBA; + const uint32 kStartCode4System = 0xBB; + const uint32 kStartCode4ProgramEnd = 0xB9; + const uint32 kPesStreamIdVideoMask = 0xF0; + const uint32 kPesStreamIdVideo = 0xE0; + const uint32 kPesStreamIdAudioMask = 0xE0; + const uint32 kPesStreamIdAudio = 0xC0; + const uint32 kVersion4 = 4; + const int kAdtsHeaderMinSize = 7; + const uint8 kAacSampleSizeBits = 16; + // Applies to all video streams. + const uint8 kNaluLengthSize = 4; // unit is bytes. + // Placeholder sampling frequency for all audio streams, which + // will be overwritten after filter parsing. + const uint32 kDefaultSamplingFrequency = 100; + + enum Type { + Type_void = 0, + Type_uint8 = 1, + Type_int8 = 2, + Type_uint16 = 3, + Type_int16 = 4, + Type_uint32 = 5, + Type_int32 = 6, + Type_uint64 = 7, + Type_int64 = 8, + Type_string = 9, + Type_BinaryData = 10 + }; +} + +namespace media { +namespace wvm { + +WvmMediaParser::WvmMediaParser() : is_initialized_(false), + parse_state_(StartCode1), + is_demuxing_sample_(true), // Check this. + is_first_pack_(true), + is_psm_needed_(true), + skip_bytes_(0), + metadata_is_complete_(false), + current_program_id_(0), + pes_stream_id_(0), + prev_pes_stream_id_(0), + pes_packet_bytes_(0), + pes_flags_1_(0), + pes_flags_2_(0), + pes_header_data_bytes_(0), + timestamp_(0), + pts_(0), + dts_(0), + index_program_id_(0), + sha_context_(new SHA256_CTX()), + media_sample_(NULL), + stream_id_count_(0) { + SHA256_Init(sha_context_); +} + +void WvmMediaParser::Init(const InitCB& init_cb, + const NewSampleCB& new_sample_cb, + KeySource* decryption_key_source) { + DCHECK(!is_initialized_); + DCHECK(!init_cb.is_null()); + DCHECK(!new_sample_cb.is_null()); + + init_cb_ = init_cb; + new_sample_cb_ = new_sample_cb; +} + +bool WvmMediaParser::Parse(const uint8* buf, int size) { + uint32 num_bytes, prev_size; + num_bytes = prev_size = 0; + uint8* read_ptr = (uint8*)(&buf[0]); + uint8* end = read_ptr + size; + + while (read_ptr < end) { + switch(parse_state_) { + case StartCode1: + if (*read_ptr == kStartCode1) { + parse_state_ = StartCode2; + } + break; + case StartCode2: + if (*read_ptr == kStartCode2) { + parse_state_ = StartCode3; + } else { + parse_state_ = StartCode1; + } + break; + case StartCode3: + if (*read_ptr == kStartCode3) { + parse_state_ = StartCode4; + } else { + parse_state_ = StartCode1; + } + break; + case StartCode4: + switch (*read_ptr) { + case kStartCode4Pack: + parse_state_ = PackHeader1; + break; + case kStartCode4System: + parse_state_ = SystemHeader1; + break; + case kStartCode4ProgramEnd: + parse_state_ = ProgramEnd; + continue; + default: + parse_state_ = PesStreamId; + continue; + } + break; + case PackHeader1: + parse_state_ = PackHeader2; + break; + case PackHeader2: + parse_state_ = PackHeader3; + break; + case PackHeader3: + parse_state_ = PackHeader4; + break; + case PackHeader4: + parse_state_ = PackHeader5; + break; + case PackHeader5: + parse_state_ = PackHeader6; + break; + case PackHeader6: + parse_state_ = PackHeader7; + break; + case PackHeader7: + parse_state_ = PackHeader8; + break; + case PackHeader8: + parse_state_ = PackHeader9; + break; + case PackHeader9: + parse_state_ = PackHeader10; + break; + case PackHeader10: + skip_bytes_ = *read_ptr & 0x07; + parse_state_ = PackHeaderStuffingSkip; + break; + case SystemHeader1: + skip_bytes_ = *read_ptr; + skip_bytes_ <<= 8; + parse_state_ = SystemHeader2; + break; + case SystemHeader2: + skip_bytes_ |= *read_ptr; + parse_state_ = SystemHeaderSkip; + break; + case PackHeaderStuffingSkip: + if ((end - read_ptr) >= (int32)skip_bytes_) { + read_ptr += skip_bytes_; + skip_bytes_ = 0; + parse_state_ = StartCode1; + } else { + skip_bytes_ -= (end - read_ptr); + read_ptr = end; + } + continue; + case SystemHeaderSkip: + if ((end - read_ptr) >= (int32)skip_bytes_) { + read_ptr += skip_bytes_; + skip_bytes_ = 0; + parse_state_ = StartCode1; + } else { + uint32 remaining_size = end - read_ptr; + skip_bytes_ -= remaining_size; + read_ptr = end; + } + continue; + case PesStreamId: + pes_stream_id_ = *read_ptr; + if (!metadata_is_complete_ && + (pes_stream_id_ != kPsmStreamId) && + (pes_stream_id_ != kIndexStreamId) && + (pes_stream_id_ != kEcmStreamId) && + (pes_stream_id_ != kV2MetadataStreamId) && + (pes_stream_id_ != kPaddingStreamId)) { + metadata_is_complete_ = true; + } + parse_state_ = PesPacketLength1; + break; + case PesPacketLength1: + pes_packet_bytes_ = *read_ptr; + pes_packet_bytes_ <<= 8; + parse_state_ = PesPacketLength2; + break; + case PesPacketLength2: + pes_packet_bytes_ |= *read_ptr; + if (HAS_HEADER_EXTENSION(pes_stream_id_)) { + parse_state_ = PesExtension1; + } else { + pes_flags_1_ = pes_flags_2_ = 0; + pes_header_data_bytes_ = 0; + parse_state_ = PesPayload; + } + break; + case PesExtension1: + pes_flags_1_ = *read_ptr; + // TODO(ramjic): Check if enable_decryption_ is needed. + *read_ptr &= ~kScramblingBitsMask; + --pes_packet_bytes_; + parse_state_ = PesExtension2; + break; + case PesExtension2: + pes_flags_2_ = *read_ptr; + --pes_packet_bytes_; + parse_state_ = PesExtension3; + break; + case PesExtension3: + pes_header_data_bytes_ = *read_ptr; + --pes_packet_bytes_; + if (pes_flags_2_ & kPesOptPts) { + parse_state_ = Pts1; + } else { + parse_state_ = PesHeaderData; + } + break; + case Pts1: + timestamp_ = (*read_ptr & 0x0E); + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Pts2; + break; + case Pts2: + timestamp_ <<= 7; + timestamp_ |= *read_ptr; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Pts3; + break; + case Pts3: + timestamp_ <<= 7; + timestamp_ |= *read_ptr >> 1; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Pts4; + break; + case Pts4: + timestamp_ <<= 8; + timestamp_ |= *read_ptr; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Pts5; + break; + case Pts5: + timestamp_ <<= 7; + timestamp_ |= *read_ptr >> 1; + pts_ = timestamp_; + --pes_header_data_bytes_; + --pes_packet_bytes_; + if (pes_flags_2_ & kPesOptDts) { + parse_state_ = Dts1; + } else { + dts_ = pts_; + parse_state_ = PesHeaderData; + } + break; + case Dts1: + timestamp_ = (*read_ptr & 0x0E); + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Dts2; + break; + case Dts2: + timestamp_ <<= 7; + timestamp_ |= *read_ptr; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Dts3; + break; + case Dts3: + timestamp_ <<= 7; + timestamp_ |= *read_ptr >> 1; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Dts4; + break; + case Dts4: + timestamp_ <<= 8; + timestamp_ |= *read_ptr; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = Dts5; + break; + case Dts5: + timestamp_ <<= 7; + timestamp_ |= *read_ptr >> 1; + dts_ = timestamp_; + --pes_header_data_bytes_; + --pes_packet_bytes_; + parse_state_ = PesHeaderData; + break; + case PesHeaderData: + num_bytes = end - read_ptr; + if (num_bytes >= pes_header_data_bytes_) { + num_bytes = pes_header_data_bytes_; + parse_state_ = PesPayload; + } + pes_header_data_bytes_ -= num_bytes; + pes_packet_bytes_ -= num_bytes; + read_ptr += num_bytes; + continue; + case PesPayload: + switch (pes_stream_id_) { + case kPsmStreamId: + psm_data_.clear(); + parse_state_ = PsmPayload; + continue; + case kPaddingStreamId: + parse_state_ = Padding; + continue; + case kEcmStreamId: + ecm_.clear(); + parse_state_ = EcmPayload; + continue; + case kIndexStreamId: + parse_state_ = IndexPayload; + continue; + default: + if (!DemuxNextPes(read_ptr, false)) { + return false; + } + parse_state_ = EsPayload; + } + continue; + case PsmPayload: + num_bytes = end - read_ptr; + if (num_bytes >= pes_packet_bytes_) { + num_bytes = pes_packet_bytes_; + parse_state_ = StartCode1; + } + if (num_bytes > 0) { + pes_packet_bytes_ -= num_bytes; + prev_size = psm_data_.size(); + psm_data_.resize(prev_size + num_bytes); + memcpy(&psm_data_[prev_size], read_ptr, num_bytes); + } + read_ptr += num_bytes; + continue; + case EcmPayload: + num_bytes = end - read_ptr; + if (num_bytes >= pes_packet_bytes_) { + num_bytes = pes_packet_bytes_; + parse_state_ = StartCode1; + } + if (num_bytes > 0) { + pes_packet_bytes_ -= num_bytes; + prev_size = ecm_.size(); + ecm_.resize(prev_size + num_bytes); + memcpy(&ecm_[prev_size], read_ptr, num_bytes); + } + if ((pes_packet_bytes_ == 0) && !ecm_.empty()) { + if (!ProcessEcm(&ecm_[0], ecm_.size())) { + return(false); + } + } + read_ptr += num_bytes; + continue; + case IndexPayload: + num_bytes = end - read_ptr; + if (num_bytes >= pes_packet_bytes_) { + num_bytes = pes_packet_bytes_; + parse_state_ = StartCode1; + } + if (num_bytes > 0) { + pes_packet_bytes_ -= num_bytes; + prev_size = index_data_.size(); + index_data_.resize(prev_size + num_bytes); + memcpy(&index_data_[prev_size], read_ptr, num_bytes); + } + if (pes_packet_bytes_ == 0 && !index_data_.empty()) { + if (!metadata_is_complete_) { + if (!ParseIndexEntry()) { + return false; + } + index_program_id_++; + index_data_.clear(); + } + } + read_ptr += num_bytes; + continue; + case EsPayload: + num_bytes = end - read_ptr; + if (num_bytes >= pes_packet_bytes_) { + num_bytes = pes_packet_bytes_; + parse_state_ = StartCode1; + } + pes_packet_bytes_ -= num_bytes; + if (pes_stream_id_ != kV2MetadataStreamId) { + sample_data_.resize(sample_data_.size() + num_bytes); + memcpy(&sample_data_[sample_data_.size() - num_bytes], read_ptr, + num_bytes); + } + prev_pes_stream_id_ = pes_stream_id_; + read_ptr += num_bytes; + continue; + case Padding: + num_bytes = end - read_ptr; + if (num_bytes >= pes_packet_bytes_) { + num_bytes = pes_packet_bytes_; + parse_state_ = StartCode1; + } + pes_packet_bytes_ -= num_bytes; + read_ptr += num_bytes; + continue; + case ProgramEnd: + parse_state_ = StartCode1; + metadata_is_complete_ = true; + if (!DemuxNextPes(read_ptr, true)) { + return false; + } + Flush(); + // Reset. + dts_ = pts_ = 0; + parse_state_ = StartCode1; + prev_media_sample_data_.Reset(); + current_program_id_++; + break; + default: + break; + } + ++read_ptr; + } + return true; +} + +bool WvmMediaParser::EmitLastSample(uint32 stream_id, + scoped_refptr& new_sample) { + std::string key = base::UintToString(current_program_id_).append(":") + .append(base::UintToString(stream_id)); + std::map::iterator it = + program_demux_stream_map_.find(key); + if (it != program_demux_stream_map_.end()) { + EmitSample(stream_id, (*it).second, new_sample, true); + } else { + return false; + } + return true; +} + +void WvmMediaParser::EmitPendingSamples() { + // Emit queued samples which were built when not initialized. + while (!media_sample_queue_.empty()) { + DemuxStreamIdMediaSample& demux_stream_media_sample = + media_sample_queue_.front(); + EmitSample( + demux_stream_media_sample.parsed_audio_or_video_stream_id, + demux_stream_media_sample.demux_stream_id, + demux_stream_media_sample.media_sample, false); + media_sample_queue_.pop_front(); + } +} + +void WvmMediaParser::Flush() { + // Flush the last audio and video sample for current program. + // Reset the streamID when successfully emitted. + if (prev_media_sample_data_.audio_sample != NULL) { + if (!EmitLastSample(prev_pes_stream_id_, + prev_media_sample_data_.audio_sample)) { + LOG(ERROR) << "Did not emit last sample for audio stream with ID = " + << prev_pes_stream_id_; + } + } + if (prev_media_sample_data_.video_sample != NULL) { + if (!EmitLastSample(prev_pes_stream_id_, + prev_media_sample_data_.video_sample)) { + LOG(ERROR) << "Did not emit last sample for video stream with ID = " + << prev_pes_stream_id_; + } + } +} + +bool WvmMediaParser::ParseIndexEntry() { + // Do not parse index entry at the beginning of any track *after* the first + // track. + if (current_program_id_ > 0) { + return true; + } + uint32 index_size = 0; + if (index_data_.size() < kIndexVersion4HeaderSize) { + return false; + } + if (sha_context_ != NULL) { + if (SHA256_Update(sha_context_, &index_data_[0], index_data_.size()) != 1) { + return false; + } + } + + const uint8* read_ptr_index = &index_data_[0]; + if (ntohlFromBuffer(read_ptr_index) != kIndexMagic) { + index_data_.clear(); + return false; + } + read_ptr_index += 4; + + uint32 version = ntohlFromBuffer(read_ptr_index); + read_ptr_index += 4; + if (version == kVersion4) { + index_size = kIndexVersion4HeaderSize + ntohlFromBuffer(read_ptr_index); + if (index_data_.size() < index_size) { + return false; + } + read_ptr_index += sizeof(uint32); + + // Index metadata + uint32 index_metadata_max_size = index_size - kIndexVersion4HeaderSize; + if (index_metadata_max_size < sizeof(uint8)) { + index_data_.clear(); + return false; + } + + uint64 track_duration = 0; + uint32 sampling_frequency = kDefaultSamplingFrequency; + uint32 time_scale = kMpeg2ClockRate; + uint16 video_width = 0; + uint16 video_height = 0; + uint8 nalu_length_size = kNaluLengthSize; + uint8 num_channels = 0; + int audio_pes_stream_id = 0; + int video_pes_stream_id = 0; + bool has_video = false; + bool has_audio = false; + std::vector decoder_config_record; + std::string video_codec_string; + std::string audio_codec_string; + uint8 num_index_entries = *read_ptr_index; + ++read_ptr_index; + --index_metadata_max_size; + + for (uint8 idx = 0; idx < num_index_entries; ++idx) { + if (index_metadata_max_size < (2 * sizeof(uint8)) + sizeof(uint32)) { + return false; + } + uint8 tag = *read_ptr_index; + ++read_ptr_index; + uint8 type = *read_ptr_index; + ++read_ptr_index; + uint32 length = ntohlFromBuffer(read_ptr_index); + read_ptr_index += sizeof(uint32); + index_metadata_max_size -= (2 * sizeof(uint8)) + sizeof(uint32); + if (index_metadata_max_size < length) { + return false; + } + int value = 0; + Tag tagtype = Unset; + std::vector binary_data(length); + switch (Type(type)) { + case Type_uint8: + if (length == sizeof(uint8)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_int8: + if (length == sizeof(int8)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_uint16: + if (length == sizeof(uint16)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_int16: + if (length == sizeof(int16)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_uint32: + if (length == sizeof(uint32)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_int32: + if (length == sizeof(int32)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_uint64: + if (length == sizeof(uint64)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_int64: + if (length == sizeof(int64)) { + tagtype = GetTag(tag, length, read_ptr_index, &value); + } else { + return false; + } + break; + case Type_string: + case Type_BinaryData: + memcpy(&binary_data[0], read_ptr_index, length); + tagtype = Tag(tag); + break; + default: + break; + } + + switch (tagtype) { + case TrackDuration: + track_duration = value; + break; + case VideoStreamId: + video_pes_stream_id = value; + break; + case AudioStreamId: + audio_pes_stream_id = value; + break; + case VideoWidth: + video_width = (uint16)value; + break; + case VideoHeight: + video_height = (uint16)value; + break; + case AudioNumChannels: + num_channels = (uint8)value; + break; + case VideoType: + has_video = true; + break; + case AudioType: + has_audio = true; + break; + default: + break; + } + + read_ptr_index += length; + index_metadata_max_size -= length; + } + // End Index metadata + index_size = read_ptr_index - &index_data_[0]; + + // Extra data for both audio and video streams not set here, but in + // Output(). + if (has_video) { + VideoCodec video_codec = kCodecH264; + stream_infos_.push_back(new VideoStreamInfo( + stream_id_count_, time_scale, track_duration, video_codec, + video_codec_string, std::string(), video_width, video_height, + nalu_length_size, NULL, 0, true)); + program_demux_stream_map_[base::UintToString(index_program_id_) + + ":" + + base::UintToString(video_pes_stream_id)] + = stream_id_count_++; + } + if (has_audio) { + AudioCodec audio_codec = kCodecAAC; + stream_infos_.push_back(new AudioStreamInfo( + stream_id_count_, time_scale, track_duration, audio_codec, + audio_codec_string, std::string(), kAacSampleSizeBits, num_channels, + sampling_frequency, NULL, 0, true)); + program_demux_stream_map_[base::UintToString(index_program_id_) + + ":" + + base::UintToString(audio_pes_stream_id)] + = stream_id_count_++; + } + } + return true; +} + +bool WvmMediaParser::DemuxNextPes(uint8* read_ptr, bool is_program_end) { + // Demux media sample if we are at program end or if we are not at a + // continuation PES. + if (is_program_end || (pes_flags_2_ & kPesOptPts)) { + if (!sample_data_.empty()) { + if (!Output()) { + return false; + } + } + StartMediaSampleDemux(read_ptr); + } + return true; +} + +void WvmMediaParser::StartMediaSampleDemux(uint8* read_ptr) { + bool is_key_frame = ((pes_flags_1_ & kPesOptAlign) != 0); + media_sample_ = MediaSample::CreateEmptyMediaSample(); + media_sample_->set_dts(dts_); + media_sample_->set_pts(pts_); + media_sample_->set_is_key_frame(is_key_frame); + + sample_data_.clear(); +} + +bool WvmMediaParser::Output() { + if ((prev_pes_stream_id_ & kPesStreamIdVideoMask) == kPesStreamIdVideo) { + // Set data on the video stream from the NalUnitStream. + std::vector nal_unit_stream; + byte_to_unit_stream_converter_.ConvertByteStreamToNalUnitStream( + &sample_data_[0], sample_data_.size(), &nal_unit_stream); + media_sample_->set_data(nal_unit_stream.data(), nal_unit_stream.size()); + if (!is_initialized_) { + // Set extra data for video stream from AVC Decoder Config Record. + // Also, set codec string from the AVC Decoder Config Record. + std::vector decoder_config_record; + byte_to_unit_stream_converter_.GetAVCDecoderConfigurationRecord( + &decoder_config_record); + for (uint32 i = 0; i < stream_infos_.size(); i++) { + if (stream_infos_[i]->stream_type() == media::kStreamVideo && + stream_infos_[i]->extra_data().empty()) { + stream_infos_[i]->set_extra_data(decoder_config_record); + stream_infos_[i]->set_codec_string(VideoStreamInfo::GetCodecString( + kCodecH264, decoder_config_record[1], decoder_config_record[2], + decoder_config_record[3])); + } + } + } + } else if ((prev_pes_stream_id_ & kPesStreamIdAudioMask) == + kPesStreamIdAudio) { + // Set data on the audio stream from AdtsHeader. + int frame_size = + media::mp2t::AdtsHeader::GetAdtsFrameSize(&sample_data_[0], + kAdtsHeaderMinSize); + media::mp2t::AdtsHeader adts_header; + const uint8* frame_ptr = &sample_data_[0]; + std::vector extra_data; + if (adts_header.Parse(frame_ptr, frame_size) && + (adts_header.GetAudioSpecificConfig(&extra_data))) { + size_t header_size = adts_header.GetAdtsHeaderSize(frame_ptr, + frame_size); + media_sample_->set_data(frame_ptr + header_size, + frame_size - header_size); + if (!is_initialized_) { + uint32 sampling_frequency = adts_header.GetSamplingFrequency(); + for (uint32 i = 0; i < stream_infos_.size(); i++) { + AudioStreamInfo* audio_stream_info = + reinterpret_cast( + stream_infos_[i].get()); + audio_stream_info->set_sampling_frequency(sampling_frequency); + // Set extra data and codec string on the audio stream from the + // AdtsHeader. + if (stream_infos_[i]->stream_type() == media::kStreamAudio && + stream_infos_[i]->extra_data().empty()) { + stream_infos_[i]->set_extra_data(extra_data); + stream_infos_[i]->set_codec_string( + AudioStreamInfo::GetCodecString( + kCodecAAC, adts_header.GetObjectType())); + } + } + } + } + } + + if (!is_initialized_) { + bool is_extra_data_in_stream_infos = true; + // Check if all collected stream infos have extra_data set. + for (uint32 i = 0; i < stream_infos_.size(); i++) { + if (stream_infos_[i]->extra_data().empty()) { + is_extra_data_in_stream_infos = false; + break; + } + } + if (is_extra_data_in_stream_infos) { + init_cb_.Run(stream_infos_); + is_initialized_ = true; + } + } + + DCHECK_GT(media_sample_->data_size(), 0UL); + std::string key = base::UintToString(current_program_id_).append(":") + .append(base::UintToString(prev_pes_stream_id_)); + std::map::iterator it = + program_demux_stream_map_.find(key); + if (it == program_demux_stream_map_.end()) { + // TODO(ramjic): Log error message here and in other error cases through + // this method. + return false; + } + DemuxStreamIdMediaSample demux_stream_media_sample; + demux_stream_media_sample.parsed_audio_or_video_stream_id = + prev_pes_stream_id_; + demux_stream_media_sample.demux_stream_id = (*it).second; + demux_stream_media_sample.media_sample = media_sample_; + // Check if sample can be emitted. + if (!is_initialized_) { + media_sample_queue_.push_back(demux_stream_media_sample); + } else { + // flush the sample queue and emit all queued samples. + while (!media_sample_queue_.empty()) { + EmitPendingSamples(); + } + // Emit current sample. + EmitSample(prev_pes_stream_id_, (*it).second, media_sample_, false); + } + return true; +} + +void WvmMediaParser::EmitSample( + uint32 parsed_audio_or_video_stream_id, uint32 stream_id, + scoped_refptr& new_sample, bool isLastSample) { + DCHECK(new_sample); + if (isLastSample) { + if ((parsed_audio_or_video_stream_id & kPesStreamIdVideoMask) + == kPesStreamIdVideo) { + new_sample->set_duration(prev_media_sample_data_.video_sample_duration); + } else if ((parsed_audio_or_video_stream_id & kPesStreamIdAudioMask) + == kPesStreamIdAudio) { + new_sample->set_duration(prev_media_sample_data_.audio_sample_duration); + } + new_sample_cb_.Run(stream_id, new_sample); + return; + } + + // Cannot emit current sample. Compute duration first and then, + // emit previous sample. + if ((parsed_audio_or_video_stream_id & kPesStreamIdVideoMask) + == kPesStreamIdVideo) { + if (prev_media_sample_data_.video_sample == NULL) { + prev_media_sample_data_.video_sample = new_sample; + prev_media_sample_data_.video_stream_id = stream_id; + return; + } + prev_media_sample_data_.video_sample->set_duration( + new_sample->dts() - prev_media_sample_data_.video_sample->dts()); + prev_media_sample_data_.video_sample_duration = + prev_media_sample_data_.video_sample->duration(); + new_sample_cb_.Run(prev_media_sample_data_.video_stream_id, + prev_media_sample_data_.video_sample); + prev_media_sample_data_.video_sample = new_sample; + prev_media_sample_data_.video_stream_id = stream_id; + } else if ((parsed_audio_or_video_stream_id & kPesStreamIdAudioMask) + == kPesStreamIdAudio) { + if (prev_media_sample_data_.audio_sample == NULL) { + prev_media_sample_data_.audio_sample = new_sample; + prev_media_sample_data_.audio_stream_id = stream_id; + return; + } + prev_media_sample_data_.audio_sample->set_duration( + new_sample->dts() - prev_media_sample_data_.audio_sample->dts()); + prev_media_sample_data_.audio_sample_duration = + prev_media_sample_data_.audio_sample->duration(); + new_sample_cb_.Run(prev_media_sample_data_.audio_stream_id, + prev_media_sample_data_.audio_sample); + prev_media_sample_data_.audio_sample = new_sample; + prev_media_sample_data_.audio_stream_id = stream_id; + } +} + +} // namespace wvm +} // namespace media diff --git a/media/formats/wvm/wvm_media_parser.h b/media/formats/wvm/wvm_media_parser.h new file mode 100644 index 0000000000..bb40d6b317 --- /dev/null +++ b/media/formats/wvm/wvm_media_parser.h @@ -0,0 +1,260 @@ +// Copyright 2014 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. +// Media parser for a Widevine Media Format (WVM) file. + +#ifndef MEDIA_FORMATS_WVM_WVM_MEDIA_PARSER_H_ +#define MEDIA_FORMATS_WVM_WVM_MEDIA_PARSER_H_ + +#include +#include +#include +#include +#include "base/memory/scoped_ptr.h" +#include "media/base/media_parser.h" +#include "media/base/audio_stream_info.h" +#include "media/base/video_stream_info.h" +#include "media/base/media_sample.h" +#include "media/base/network_util.h" +#include "media/filters/h264_byte_to_unit_stream_converter.h" +#include "openssl/sha.h" + +namespace media { +namespace wvm { + +struct DemuxStreamIdMediaSample { + uint32 demux_stream_id; + uint32 parsed_audio_or_video_stream_id; + scoped_refptr media_sample; +}; + +struct PrevSampleData { + public: + PrevSampleData() { Reset(); } + void Reset() { + audio_sample = video_sample = NULL; + audio_stream_id = video_stream_id = 0; + audio_sample_duration = video_sample_duration = 0; + } + scoped_refptr audio_sample; + scoped_refptr video_sample; + uint32 audio_stream_id; + uint32 video_stream_id; + int64 audio_sample_duration; + int64 video_sample_duration; +}; + +class WvmMediaParser : public MediaParser { + public: + WvmMediaParser(); + virtual ~WvmMediaParser() {} + + // MediaParser implementation overrides. + virtual void Init(const InitCB& init_cb, + const NewSampleCB& new_sample_cb, + KeySource* decryption_key_source) OVERRIDE; + + virtual void Flush() OVERRIDE; + + virtual bool Parse(const uint8* buf, int size) OVERRIDE; + + private: + enum Tag { + CypherVersion = 0, + TrackOffset = 1, + TrackSize = 2, + TrackDuration = 3, + TrackBitRate = 4, + TrackTrickPlayRate = 5, + TrackAdaptationInterval = 6, + TrackFlags = 7, + VideoType = 8, + VideoProfile = 9, + VideoLevel = 10, + VideoWidth = 11, + VideoHeight = 12, + VideoTicksPerFrame = 13, + VideoBitRate = 14, + AudioType = 15, + AudioProfile = 16, + AudioNumChannels = 17, + AudioSampleFrequency = 18, + AudioBitRate = 19, + TrackVersion = 20, + Title = 21, + Copyright = 22, + ChapterIndex = 23, + TimeIndex = 24, + Thumbnail = 25, + ObjectSeqNum = 26, + ThumbnailOffset = 27, + ThumbnailSize = 28, + NumEntries = 29, + Chapters = 30, + VideoPixelWidth = 31, + VideoPixelHeight = 32, + FileSize = 33, + SparseDownloadUrl = 34, + SparseDownloadRangeTranslations = 35, + SparseDownloadMap = 36, + AudioSampleSize = 37, + Audio_EsDescriptor = 38, + AVCDecoderConfigurationRecord = 39, + Audio_EC3SpecificData = 40, + AudioIdentifier = 41, + VideoStreamId = 42, + VideoStreamType = 43, + AudioStreamId = 44, + AudioStreamType = 45, + Audio_DtsSpecificData = 46, + Audio_AC3SpecificData = 47, + Unset = 48 + }; + + enum State { + StartCode1 = 0, + StartCode2, + StartCode3, + StartCode4, + PackHeader1, + PackHeader2, + PackHeader3, + PackHeader4, + PackHeader5, + PackHeader6, + PackHeader7, + PackHeader8, + PackHeader9, + PackHeader10, + PackHeaderStuffingSkip, + SystemHeader1, + SystemHeader2, + SystemHeaderSkip, + PesStreamId, + PesPacketLength1, + PesPacketLength2, + PesExtension1, + PesExtension2, + PesExtension3, + Pts1, + Pts2, + Pts3, + Pts4, + Pts5, + Dts1, + Dts2, + Dts3, + Dts4, + Dts5, + PesHeaderData, + PesPayload, + EsPayload, + PsmPayload, + EcmPayload, + IndexPayload, + Padding, + ProgramEnd + }; + + bool DecryptCBC(void* data, uint32 length, uint32 bytesRemaining, + uint32& bytesDecrypted) { + return(true); + } + + bool ProcessEcm(void* ecm, uint32 size) { + return(true); + } + + // Index denotes 'search index' in the WVM content. + bool ParseIndexEntry(); + + bool DemuxNextPes(uint8* start, bool is_program_end); + + void StartMediaSampleDemux(uint8* start); + + template + Tag GetTag(const uint8& tag, const uint32& length, + const uint8* start_index, T* value) { + if (length == sizeof(uint8)) { + *value = (uint8)(*start_index); + } else if (length == sizeof(int8)) { + *value = (int8)(*start_index); + } else if (length == sizeof(uint16)) { + *value = (uint16)(ntohsFromBuffer(start_index)); + } else if (length == sizeof(int16)) { + *value = (int16)(ntohsFromBuffer(start_index)); + } else if (length == sizeof(uint32)) { + *value = (uint32)(ntohlFromBuffer(start_index)); + } else if (length == sizeof(int32)) { + *value = (int32)(ntohlFromBuffer(start_index)); + } else if (length == sizeof(uint64)) { + *value = (uint64)(ntohllFromBuffer(start_index)); + } else if (length == sizeof(int64)) { + *value = (int64)(ntohllFromBuffer(start_index)); + } else { + *value = 0; + } + return Tag(tag); + } + + bool Output(); + + // Callback invoked by the ES media parser + // to emit a new audio/video access unit. + void EmitSample( + uint32 parsed_audio_or_video_stream_id, uint32 stream_id, + scoped_refptr& new_sample, bool isLastSample); + + void EmitPendingSamples(); + + bool EmitLastSample(uint32 stream_id, scoped_refptr& new_sample); + + // List of callbacks.t + InitCB init_cb_; + NewSampleCB new_sample_cb_; + + // Whether |init_cb_| has been invoked. + bool is_initialized_; + // Internal content parsing state. + State parse_state_; + + bool is_demuxing_sample_; + bool is_first_pack_; + + bool is_psm_needed_; + uint32 skip_bytes_; + bool metadata_is_complete_; + uint8 current_program_id_; + uint32 pes_stream_id_; + uint32 prev_pes_stream_id_; + uint16 pes_packet_bytes_; + uint8 pes_flags_1_; + uint8 pes_flags_2_; + uint8 pes_header_data_bytes_; + uint64 timestamp_; + uint64 pts_; + uint64 dts_; + uint8 index_program_id_; + + SHA256_CTX* sha_context_; + scoped_refptr media_sample_; + PrevSampleData prev_media_sample_data_; + + H264ByteToUnitStreamConverter byte_to_unit_stream_converter_; + + std::vector > ecm_; + std::vector psm_data_; + std::vector index_data_; + std::map program_demux_stream_map_; + int stream_id_count_; + std::vector > stream_infos_; + std::deque media_sample_queue_; + std::vector sample_data_; + + DISALLOW_COPY_AND_ASSIGN(WvmMediaParser); +}; + +} // namespace wvm +} // namespace media + +#endif // MEDIA_FORMATS_WVM_WVM_MEDIA_PARSER_H_ diff --git a/packager.gyp b/packager.gyp index cc6c69775e..e13a355345 100644 --- a/packager.gyp +++ b/packager.gyp @@ -36,6 +36,7 @@ 'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp4/mp4.gyp:mp4', 'media/formats/mpeg/mpeg.gyp:mpeg', + 'media/formats/wvm/wvm.gyp:wvm', 'mpd/mpd.gyp:mpd_builder', 'third_party/gflags/gflags.gyp:gflags', 'third_party/openssl/openssl.gyp:openssl', @@ -73,6 +74,7 @@ 'media/formats/mp2t/mp2t.gyp:mp2t', 'media/formats/mp4/mp4.gyp:mp4', 'media/formats/mpeg/mpeg.gyp:mpeg', + 'media/formats/wvm/wvm.gyp:wvm', 'media/test/media_test.gyp:media_test_support', 'testing/gtest.gyp:gtest', ], @@ -86,6 +88,7 @@ 'media/file/file.gyp:*', 'media/formats/mp2t/mp2t.gyp:*', 'media/formats/mp4/mp4.gyp:*', + 'media/formats/wvm/wvm.gyp:*', 'mpd/mpd.gyp:*', ], }, @@ -99,6 +102,7 @@ 'media/filters/filters.gyp:filters_unittest', 'media/formats/mp2t/mp2t.gyp:mp2t_unittest', 'media/formats/mp4/mp4.gyp:mp4_unittest', +# 'media/formats/wvm/wvm.gyp:wvm_unittest', 'mpd/mpd.gyp:mpd_unittest', 'packager_test', ],