diff --git a/README.md b/README.md index d38edd9a1e..0592b30168 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Current supported codecs: | Codecs | ISO-BMFF | WebM | MPEG2-TS | WVM | |:-----------------:|:------------:|:------------:|:------------:|:-----------:| | H264 (AVC) | I / O | - | I | I | -| H265 (HEVC) | I / O | - | - | - | +| H265 (HEVC) | I / O | - | I | - | | VP8 | I / O | I / O | - | - | | VP9 | I / O | I / O | - | - | | AAC | I / O | - | I | I | diff --git a/packager/media/filters/nalu_reader.cc b/packager/media/filters/nalu_reader.cc index 335789280d..d0dfa2ee87 100644 --- a/packager/media/filters/nalu_reader.cc +++ b/packager/media/filters/nalu_reader.cc @@ -88,6 +88,10 @@ bool Nalu::InitializeFromH264(const uint8_t* data, uint64_t size) { is_video_slice_ = (type_ >= Nalu::H264_NonIDRSlice && type_ <= Nalu::H264_IDRSlice); + can_start_access_unit_ = + (is_video_slice_ || type_ == Nalu::H264_AUD || type_ == Nalu::H264_SPS || + type_ == Nalu::H264_PPS || type_ == Nalu::H264_SEIMessage || + (type_ >= Nalu::H264_PrefixNALUnit && type_ <= Nalu::H264_Reserved18)); return true; } @@ -150,6 +154,13 @@ bool Nalu::InitializeFromH265(const uint8_t* data, uint64_t size) { } is_video_slice_ = type_ >= Nalu::H265_TRAIL_N && type_ <= Nalu::H265_CRA_NUT; + can_start_access_unit_ = + nuh_layer_id_ == 0 && + (is_video_slice_ || type_ == Nalu::H265_AUD || type_ == Nalu::H265_VPS || + type_ == Nalu::H265_SPS || type_ == Nalu::H265_PPS || + type_ == Nalu::H265_PREFIX_SEI || + (type_ >= Nalu::H265_RSV_NVCL41 && type_ <= Nalu::H265_RSV_NVCL44) || + (type_ >= Nalu::H265_UNSPEC48 && type_ <= Nalu::H265_UNSPEC55)); return true; } diff --git a/packager/media/filters/nalu_reader.h b/packager/media/filters/nalu_reader.h index 64f570c04f..454677aa4c 100644 --- a/packager/media/filters/nalu_reader.h +++ b/packager/media/filters/nalu_reader.h @@ -36,7 +36,9 @@ class Nalu { H264_EOSeq = 10, H264_FillerData = 12, H264_SPSExtension = 13, + H264_PrefixNALUnit = 14, H264_SubsetSPS = 15, + H264_DepthParameterSet = 16, H264_Reserved17 = 17, H264_Reserved18 = 18, H264_CodedSliceExtension = 20, @@ -70,7 +72,12 @@ class Nalu { H265_EOS = 36, H265_EOB = 37, + H265_PREFIX_SEI = 39, + H265_RSV_NVCL41 = 41, + H265_RSV_NVCL44 = 44, + H265_UNSPEC48 = 48, + H265_UNSPEC55 = 55, }; enum CodecType { kH264, @@ -96,6 +103,7 @@ class Nalu { int type() const { return type_; } bool is_video_slice() const { return is_video_slice_; } + bool can_start_access_unit() const { return can_start_access_unit_; } private: bool InitializeFromH264(const uint8_t* data, uint64_t size); @@ -115,6 +123,7 @@ class Nalu { int nuh_temporal_id_; int type_; bool is_video_slice_; + bool can_start_access_unit_; // Don't use DISALLOW_COPY_AND_ASSIGN since it is just numbers and a pointer // it does not own. This allows Nalus to be stored in a vector. diff --git a/packager/media/formats/mp2t/es_parser_h264.cc b/packager/media/formats/mp2t/es_parser_h264.cc index 6e94fc6960..a34f298a3a 100644 --- a/packager/media/formats/mp2t/es_parser_h264.cc +++ b/packager/media/formats/mp2t/es_parser_h264.cc @@ -7,9 +7,7 @@ #include #include "packager/base/logging.h" -#include "packager/base/numerics/safe_conversions.h" #include "packager/media/base/media_sample.h" -#include "packager/media/base/offset_byte_queue.h" #include "packager/media/base/timestamp.h" #include "packager/media/base/video_stream_info.h" #include "packager/media/filters/avc_decoder_configuration.h" @@ -21,314 +19,98 @@ namespace edash_packager { namespace media { namespace mp2t { -namespace { - -// An AUD NALU is at least 4 bytes: -// 3 bytes for the start code + 1 byte for the NALU type. -const int kMinAUDSize = 4; - -} // anonymous namespace - EsParserH264::EsParserH264(uint32_t pid, const NewStreamInfoCB& new_stream_info_cb, const EmitSampleCB& emit_sample_cb) - : EsParser(pid), + : EsParserH26x(Nalu::kH264, pid, emit_sample_cb), new_stream_info_cb_(new_stream_info_cb), - emit_sample_cb_(emit_sample_cb), - es_queue_(new media::OffsetByteQueue()), - h264_parser_(new H264Parser()), - current_access_unit_pos_(0), - next_access_unit_pos_(0), - stream_converter_(new H264ByteToUnitStreamConverter), decoder_config_check_pending_(false), - pending_sample_duration_(0), - waiting_for_key_frame_(true) { -} + h264_parser_(new H264Parser()) {} -EsParserH264::~EsParserH264() { -} - -bool EsParserH264::Parse(const uint8_t* buf, - int size, - int64_t pts, - int64_t dts) { - // Note: Parse is invoked each time a PES packet has been reassembled. - // Unfortunately, a PES packet does not necessarily map - // to an h264 access unit, although the HLS recommendation is to use one PES - // for each access unit (but this is just a recommendation and some streams - // do not comply with this recommendation). - - // HLS recommendation: "In AVC video, you should have both a DTS and a - // PTS in each PES header". - // However, some streams do not comply with this recommendation. - DVLOG_IF(1, pts == kNoTimestamp) << "Each video PES should have a PTS"; - if (pts != kNoTimestamp) { - TimingDesc timing_desc; - timing_desc.pts = pts; - timing_desc.dts = (dts != kNoTimestamp) ? dts : pts; - - // Link the end of the byte queue with the incoming timing descriptor. - timing_desc_list_.push_back( - std::pair(es_queue_->tail(), timing_desc)); - } - - // Add the incoming bytes to the ES queue. - es_queue_->Push(buf, size); - return ParseInternal(); -} - -void EsParserH264::Flush() { - DVLOG(1) << "EsParserH264::Flush"; - - if (FindAUD(¤t_access_unit_pos_)) { - // Simulate an additional AUD to force emitting the last access unit - // which is assumed to be complete at this point. - uint8_t aud[] = {0x00, 0x00, 0x01, 0x09}; - es_queue_->Push(aud, sizeof(aud)); - ParseInternal(); - } - - if (pending_sample_) { - // Flush pending sample. - DCHECK(pending_sample_duration_); - pending_sample_->set_duration(pending_sample_duration_); - emit_sample_cb_.Run(pid(), pending_sample_); - pending_sample_ = scoped_refptr(); - } -} +EsParserH264::~EsParserH264() {} void EsParserH264::Reset() { DVLOG(1) << "EsParserH264::Reset"; - es_queue_.reset(new media::OffsetByteQueue()); h264_parser_.reset(new H264Parser()); - current_access_unit_pos_ = 0; - next_access_unit_pos_ = 0; - timing_desc_list_.clear(); last_video_decoder_config_ = scoped_refptr(); decoder_config_check_pending_ = false; - pending_sample_ = scoped_refptr(); - pending_sample_duration_ = 0; - waiting_for_key_frame_ = true; + EsParserH26x::Reset(); } -bool EsParserH264::FindAUD(int64_t* stream_pos) { - while (true) { - const uint8_t* es; - int size; - es_queue_->PeekAt(*stream_pos, &es, &size); - - // Find a start code and move the stream to the start code parser position. - uint64_t start_code_offset; - uint8_t start_code_size; - bool start_code_found = NaluReader::FindStartCode( - es, size, &start_code_offset, &start_code_size); - *stream_pos += start_code_offset; - - // No H264 start code found or NALU type not available yet. - if (!start_code_found || - start_code_offset + start_code_size >= static_cast(size)) { - return false; - } - - // Exit the parser loop when an AUD is found. - // Note: NALU header for an AUD: - // - ref_idc must be 0 - // - type must be Nalu::H264_AUD - if (es[start_code_offset + start_code_size] == Nalu::H264_AUD) +bool EsParserH264::ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) { + switch (nalu.type()) { + case Nalu::H264_AUD: { + DVLOG(LOG_LEVEL_ES) << "Nalu: AUD"; break; - - // The current NALU is not an AUD, skip the start code - // and continue parsing the stream. - *stream_pos += start_code_size; - } - - return true; -} - -bool EsParserH264::ParseInternal() { - DCHECK_LE(es_queue_->head(), current_access_unit_pos_); - DCHECK_LE(current_access_unit_pos_, next_access_unit_pos_); - DCHECK_LE(next_access_unit_pos_, es_queue_->tail()); - - // Find the next AUD located at or after |current_access_unit_pos_|. This is - // needed since initially |current_access_unit_pos_| might not point to - // an AUD. - // Discard all the data before the updated |current_access_unit_pos_| - // since it won't be used again. - bool aud_found = FindAUD(¤t_access_unit_pos_); - es_queue_->Trim(current_access_unit_pos_); - if (next_access_unit_pos_ < current_access_unit_pos_) - next_access_unit_pos_ = current_access_unit_pos_; - - // Resume parsing later if no AUD was found. - if (!aud_found) - return true; - - // Find the next AUD to make sure we have a complete access unit. - if (next_access_unit_pos_ < current_access_unit_pos_ + kMinAUDSize) { - next_access_unit_pos_ = current_access_unit_pos_ + kMinAUDSize; - DCHECK_LE(next_access_unit_pos_, es_queue_->tail()); - } - if (!FindAUD(&next_access_unit_pos_)) - return true; - - // At this point, we know we have a full access unit. - bool is_key_frame = false; - int pps_id_for_access_unit = -1; - - const uint8_t* es; - int size; - es_queue_->PeekAt(current_access_unit_pos_, &es, &size); - int access_unit_size = base::checked_cast( - next_access_unit_pos_ - current_access_unit_pos_); - DCHECK_LE(access_unit_size, size); - NaluReader reader(Nalu::kH264, kIsAnnexbByteStream, es, access_unit_size); - - while (true) { - Nalu nalu; - bool is_eos = false; - switch (reader.Advance(&nalu)) { - case NaluReader::kOk: - break; - case NaluReader::kEOStream: - is_eos = true; - break; - default: + } + case Nalu::H264_SPS: { + DVLOG(LOG_LEVEL_ES) << "Nalu: SPS"; + int sps_id; + if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk) return false; - } - if (is_eos) + decoder_config_check_pending_ = true; break; - - switch (nalu.type()) { - case Nalu::H264_AUD: { - DVLOG(LOG_LEVEL_ES) << "Nalu: AUD"; - break; - } - case Nalu::H264_SPS: { - DVLOG(LOG_LEVEL_ES) << "Nalu: SPS"; - int sps_id; - if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk) + } + case Nalu::H264_PPS: { + DVLOG(LOG_LEVEL_ES) << "Nalu: PPS"; + int pps_id; + if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) { + // Allow PPS parsing to fail if waiting for SPS. + if (last_video_decoder_config_) return false; + } else { decoder_config_check_pending_ = true; - break; } - case Nalu::H264_PPS: { - DVLOG(LOG_LEVEL_ES) << "Nalu: PPS"; - int pps_id; - if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) { - // Allow PPS parsing to fail if waiting for SPS. - if (last_video_decoder_config_) - return false; - } else { - decoder_config_check_pending_ = true; - } - break; - } - case Nalu::H264_IDRSlice: - case Nalu::H264_NonIDRSlice: { - is_key_frame = (nalu.type() == Nalu::H264_IDRSlice); - DVLOG(LOG_LEVEL_ES) << "Nalu: slice IDR=" << is_key_frame; - H264SliceHeader shdr; - if (h264_parser_->ParseSliceHeader(nalu, &shdr) != H264Parser::kOk) { - // Only accept an invalid SPS/PPS at the beginning when the stream - // does not necessarily start with an SPS/PPS/IDR. - if (last_video_decoder_config_) - return false; - } else { - pps_id_for_access_unit = shdr.pic_parameter_set_id; - } - break; - } - default: { - DVLOG(LOG_LEVEL_ES) << "Nalu: " << nalu.type(); + break; + } + case Nalu::H264_IDRSlice: + case Nalu::H264_NonIDRSlice: { + *is_key_frame = (nalu.type() == Nalu::H264_IDRSlice); + DVLOG(LOG_LEVEL_ES) << "Nalu: slice IDR=" << is_key_frame; + H264SliceHeader shdr; + if (h264_parser_->ParseSliceHeader(nalu, &shdr) != H264Parser::kOk) { + // Only accept an invalid SPS/PPS at the beginning when the stream + // does not necessarily start with an SPS/PPS/IDR. + if (last_video_decoder_config_) + return false; + } else { + *pps_id_for_access_unit = shdr.pic_parameter_set_id; } + break; + } + default: { + DVLOG(LOG_LEVEL_ES) << "Nalu: " << nalu.type(); } } - if (waiting_for_key_frame_) { - waiting_for_key_frame_ = !is_key_frame; - } - if (!waiting_for_key_frame_) { - // Emit a frame and move the stream to the next AUD position. - RCHECK(EmitFrame(current_access_unit_pos_, access_unit_size, - is_key_frame, pps_id_for_access_unit)); - } - current_access_unit_pos_ = next_access_unit_pos_; - es_queue_->Trim(current_access_unit_pos_); - return true; } -bool EsParserH264::EmitFrame(int64_t access_unit_pos, - int access_unit_size, - bool is_key_frame, - int pps_id) { - // Get the access unit timing info. - TimingDesc current_timing_desc = {kNoTimestamp, kNoTimestamp}; - while (!timing_desc_list_.empty() && - timing_desc_list_.front().first <= access_unit_pos) { - current_timing_desc = timing_desc_list_.front().second; - timing_desc_list_.pop_front(); - } - if (current_timing_desc.pts == kNoTimestamp) - return false; +bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) { + // Update the video decoder configuration if needed. + if (!decoder_config_check_pending_) + return true; - // Emit a frame. - DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_ - << " size=" << access_unit_size; - int es_size; - const uint8_t* es; - es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size); - CHECK_GE(es_size, access_unit_size); - - // Convert frame to unit stream format. - std::vector converted_frame; - if (!stream_converter_->ConvertByteStreamToNalUnitStream( - es, access_unit_size, &converted_frame)) { - DLOG(ERROR) << "Failure to convert video frame to unit stream format."; - return false; + const H264Pps* pps = h264_parser_->GetPps(pps_id); + const H264Sps* sps; + if (!pps) { + // Only accept an invalid PPS at the beginning when the stream + // does not necessarily start with an SPS/PPS/IDR. + // In this case, the initial frames are conveyed to the upper layer with + // an invalid VideoDecoderConfig and it's up to the upper layer + // to process this kind of frame accordingly. + return last_video_decoder_config_ == nullptr; + } else { + sps = h264_parser_->GetSps(pps->seq_parameter_set_id); + if (!sps) + return false; + decoder_config_check_pending_ = false; } - if (decoder_config_check_pending_) { - // Update the video decoder configuration if needed. - const H264Pps* pps = h264_parser_->GetPps(pps_id); - if (!pps) { - // Only accept an invalid PPS at the beginning when the stream - // does not necessarily start with an SPS/PPS/IDR. - // In this case, the initial frames are conveyed to the upper layer with - // an invalid VideoDecoderConfig and it's up to the upper layer - // to process this kind of frame accordingly. - if (last_video_decoder_config_) - return false; - } else { - const H264Sps* sps = h264_parser_->GetSps(pps->seq_parameter_set_id); - if (!sps) - return false; - RCHECK(UpdateVideoDecoderConfig(sps)); - decoder_config_check_pending_ = false; - } - } - - // Create the media sample, emitting always the previous sample after - // calculating its duration. - scoped_refptr media_sample = MediaSample::CopyFrom( - converted_frame.data(), converted_frame.size(), is_key_frame); - media_sample->set_dts(current_timing_desc.dts); - media_sample->set_pts(current_timing_desc.pts); - if (pending_sample_) { - DCHECK_GT(media_sample->dts(), pending_sample_->dts()); - pending_sample_duration_ = media_sample->dts() - pending_sample_->dts(); - pending_sample_->set_duration(pending_sample_duration_); - emit_sample_cb_.Run(pid(), pending_sample_); - } - pending_sample_ = media_sample; - - return true; -} - -bool EsParserH264::UpdateVideoDecoderConfig(const H264Sps* sps) { std::vector decoder_config_record; - if (!stream_converter_->GetDecoderConfigurationRecord( + if (!stream_converter()->GetDecoderConfigurationRecord( &decoder_config_record)) { DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord"; return false; diff --git a/packager/media/formats/mp2t/es_parser_h264.h b/packager/media/formats/mp2t/es_parser_h264.h index e56ca0f5b5..924119fff9 100644 --- a/packager/media/formats/mp2t/es_parser_h264.h +++ b/packager/media/formats/mp2t/es_parser_h264.h @@ -7,21 +7,14 @@ #include -#include -#include - #include "packager/base/callback.h" -#include "packager/base/compiler_specific.h" #include "packager/base/memory/scoped_ptr.h" -#include "packager/media/formats/mp2t/es_parser.h" +#include "packager/media/formats/mp2t/es_parser_h26x.h" namespace edash_packager { namespace media { -class H264ByteToUnitStreamConverter; class H264Parser; -class OffsetByteQueue; -struct H264Sps; namespace mp2t { @@ -30,74 +23,34 @@ namespace mp2t { // Mpeg2 TS spec: "2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video" // "Each AVC access unit shall contain an access unit delimiter NAL Unit;" // -class EsParserH264 : public EsParser { +class EsParserH264 : public EsParserH26x { public: EsParserH264(uint32_t pid, const NewStreamInfoCB& new_stream_info_cb, const EmitSampleCB& emit_sample_cb); ~EsParserH264() override; - // EsParser implementation overrides. - bool Parse(const uint8_t* buf, int size, int64_t pts, int64_t dts) override; - void Flush() override; + // EsParserH26x implementation override. void Reset() override; private: - struct TimingDesc { - int64_t dts; - int64_t pts; - }; - - // Find the AUD located at or after |*stream_pos|. - // Return true if an AUD is found. - // If found, |*stream_pos| corresponds to the position of the AUD start code - // in the stream. Otherwise, |*stream_pos| corresponds to the last position - // of the start code parser. - bool FindAUD(int64_t* stream_pos); - - // Resumes the H264 ES parsing. - // Return true if successful. - bool ParseInternal(); - - // Emit a frame whose position in the ES queue starts at |access_unit_pos|. - // Returns true if successful, false if no PTS is available for the frame. - bool EmitFrame(int64_t access_unit_pos, - int access_unit_size, - bool is_key_frame, - int pps_id); + // Processes a NAL unit found in ParseInternal. The @a pps_id_for_access_unit + // value will be passed to UpdateVideoDecoderConfig. + bool ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) override; // Update the video decoder config based on an H264 SPS. // Return true if successful. - bool UpdateVideoDecoderConfig(const H264Sps* sps); + bool UpdateVideoDecoderConfig(int sps_id) override; - // Callbacks to pass the stream configuration and the frames. + // Callback to pass the stream configuration. NewStreamInfoCB new_stream_info_cb_; - EmitSampleCB emit_sample_cb_; - // Bytes of the ES stream that have not been emitted yet. - scoped_ptr es_queue_; - std::list > timing_desc_list_; - - // H264 parser state. - // - |current_access_unit_pos_| is pointing to an annexB syncword - // representing the first NALU of an H264 access unit. - scoped_ptr h264_parser_; - int64_t current_access_unit_pos_; - int64_t next_access_unit_pos_; - - // Filter to convert H.264 Annex B byte stream to unit stream. - scoped_ptr stream_converter_; - - // Last video decoder config. scoped_refptr last_video_decoder_config_; bool decoder_config_check_pending_; - // Frame for which we do not yet have a duration. - scoped_refptr pending_sample_; - uint64_t pending_sample_duration_; - - // Indicates whether waiting for first key frame. - bool waiting_for_key_frame_; + scoped_ptr h264_parser_; }; } // namespace mp2t diff --git a/packager/media/formats/mp2t/es_parser_h265.cc b/packager/media/formats/mp2t/es_parser_h265.cc new file mode 100644 index 0000000000..985231889a --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_h265.cc @@ -0,0 +1,173 @@ +// Copyright 2016 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 + +#include "packager/media/formats/mp2t/es_parser_h265.h" + +#include + +#include "packager/base/logging.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/offset_byte_queue.h" +#include "packager/media/base/timestamp.h" +#include "packager/media/base/video_stream_info.h" +#include "packager/media/filters/hevc_decoder_configuration.h" +#include "packager/media/filters/h265_parser.h" +#include "packager/media/filters/h26x_byte_to_unit_stream_converter.h" +#include "packager/media/formats/mp2t/mp2t_common.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +EsParserH265::EsParserH265(uint32_t pid, + const NewStreamInfoCB& new_stream_info_cb, + const EmitSampleCB& emit_sample_cb) + : EsParserH26x(Nalu::kH265, pid, emit_sample_cb), + new_stream_info_cb_(new_stream_info_cb), + decoder_config_check_pending_(false), + h265_parser_(new H265Parser()) {} + +EsParserH265::~EsParserH265() {} + +void EsParserH265::Reset() { + DVLOG(1) << "EsParserH265::Reset"; + h265_parser_.reset(new H265Parser()); + last_video_decoder_config_ = scoped_refptr(); + decoder_config_check_pending_ = false; + EsParserH26x::Reset(); +} + +bool EsParserH265::ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) { + switch (nalu.type()) { + case Nalu::H265_AUD: { + DVLOG(LOG_LEVEL_ES) << "Nalu: AUD"; + break; + } + case Nalu::H265_SPS: { + DVLOG(LOG_LEVEL_ES) << "Nalu: SPS"; + int sps_id; + if (h265_parser_->ParseSps(nalu, &sps_id) != H265Parser::kOk) + return false; + decoder_config_check_pending_ = true; + break; + } + case Nalu::H265_PPS: { + DVLOG(LOG_LEVEL_ES) << "Nalu: PPS"; + int pps_id; + if (h265_parser_->ParsePps(nalu, &pps_id) != H265Parser::kOk) { + // Allow PPS parsing to fail if waiting for SPS. + if (last_video_decoder_config_) + return false; + } else { + decoder_config_check_pending_ = true; + } + break; + } + default: { + if (nalu.is_video_slice()) { + *is_key_frame = nalu.type() == Nalu::H265_IDR_W_RADL || + nalu.type() == Nalu::H265_IDR_N_LP; + DVLOG(LOG_LEVEL_ES) << "Nalu: slice KeyFrame=" << is_key_frame; + H265SliceHeader shdr; + if (h265_parser_->ParseSliceHeader(nalu, &shdr) != H265Parser::kOk) { + // Only accept an invalid SPS/PPS at the beginning when the stream + // does not necessarily start with an SPS/PPS/IDR. + if (last_video_decoder_config_) + return false; + } else { + *pps_id_for_access_unit = shdr.pic_parameter_set_id; + } + } else { + DVLOG(LOG_LEVEL_ES) << "Nalu: " << nalu.type(); + } + } + } + + return true; +} + +bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) { + // Update the video decoder configuration if needed. + if (!decoder_config_check_pending_) + return true; + + const H265Pps* pps = h265_parser_->GetPps(pps_id); + const H265Sps* sps; + if (!pps) { + // Only accept an invalid PPS at the beginning when the stream + // does not necessarily start with an SPS/PPS/IDR. + // In this case, the initial frames are conveyed to the upper layer with + // an invalid VideoDecoderConfig and it's up to the upper layer + // to process this kind of frame accordingly. + return last_video_decoder_config_ == nullptr; + } else { + sps = h265_parser_->GetSps(pps->seq_parameter_set_id); + if (!sps) + return false; + decoder_config_check_pending_ = false; + } + + std::vector decoder_config_record; + HEVCDecoderConfiguration decoder_config; + if (!stream_converter()->GetDecoderConfigurationRecord( + &decoder_config_record) || + !decoder_config.Parse(decoder_config_record)) { + DLOG(ERROR) << "Failure to construct an HEVCDecoderConfigurationRecord"; + return false; + } + + if (last_video_decoder_config_) { + if (last_video_decoder_config_->extra_data() != decoder_config_record) { + // Video configuration has changed. Issue warning. + // TODO(tinskip): Check the nature of the configuration change. Only + // minor configuration changes (such as frame ordering) can be handled + // gracefully by decoders without notification. Major changes (such as + // video resolution changes) should be treated as errors. + LOG(WARNING) << "H.265 decoder configuration has changed."; + last_video_decoder_config_->set_extra_data(decoder_config_record); + } + return true; + } + + uint32_t coded_width = 0; + uint32_t coded_height = 0; + uint32_t pixel_width = 0; + uint32_t pixel_height = 0; + if (!ExtractResolutionFromSps(*sps, &coded_width, &coded_height, &pixel_width, + &pixel_height)) { + LOG(ERROR) << "Failed to parse SPS."; + return false; + } + + last_video_decoder_config_ = scoped_refptr( + new VideoStreamInfo( + pid(), + kMpeg2Timescale, + kInfiniteDuration, + kCodecHVC1, + decoder_config.GetCodecString(kCodecHVC1), + std::string(), + coded_width, + coded_height, + pixel_width, + pixel_height, + 0, + H26xByteToUnitStreamConverter::kUnitStreamNaluLengthSize, + decoder_config_record.data(), + decoder_config_record.size(), + false)); + + // Video config notification. + new_stream_info_cb_.Run(last_video_decoder_config_); + + return true; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/es_parser_h265.h b/packager/media/formats/mp2t/es_parser_h265.h new file mode 100644 index 0000000000..a4173e5b87 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_h265.h @@ -0,0 +1,62 @@ +// Copyright 2016 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 + +#ifndef MEDIA_FORMATS_MP2T_ES_PARSER_H265_H_ +#define MEDIA_FORMATS_MP2T_ES_PARSER_H265_H_ + +#include + +#include +#include + +#include "packager/base/callback.h" +#include "packager/base/compiler_specific.h" +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/formats/mp2t/es_parser_h26x.h" + +namespace edash_packager { +namespace media { + +class H265Parser; + +namespace mp2t { + +class EsParserH265 : public EsParserH26x { + public: + EsParserH265(uint32_t pid, + const NewStreamInfoCB& new_stream_info_cb, + const EmitSampleCB& emit_sample_cb); + ~EsParserH265() override; + + // EsParserH26x implementation override. + void Reset() override; + + private: + // Processes a NAL unit found in ParseInternal. The @a pps_id_for_access_unit + // value will be passed to UpdateVideoDecoderConfig. + bool ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) override; + + // Update the video decoder config based on an H264 SPS. + // Return true if successful. + bool UpdateVideoDecoderConfig(int sps_id) override; + + // Callback to pass the stream configuration. + NewStreamInfoCB new_stream_info_cb_; + + // Last video decoder config. + scoped_refptr last_video_decoder_config_; + bool decoder_config_check_pending_; + + scoped_ptr h265_parser_; +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif // MEDIA_FORMATS_MP2T_ES_PARSER_H265_H_ diff --git a/packager/media/formats/mp2t/es_parser_h26x.cc b/packager/media/formats/mp2t/es_parser_h26x.cc new file mode 100644 index 0000000000..dcc24d28b9 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_h26x.cc @@ -0,0 +1,296 @@ +// 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 "packager/media/formats/mp2t/es_parser_h26x.h" + +#include + +#include "packager/base/logging.h" +#include "packager/base/numerics/safe_conversions.h" +#include "packager/media/base/media_sample.h" +#include "packager/media/base/offset_byte_queue.h" +#include "packager/media/base/timestamp.h" +#include "packager/media/base/video_stream_info.h" +#include "packager/media/filters/h264_byte_to_unit_stream_converter.h" +#include "packager/media/filters/h265_byte_to_unit_stream_converter.h" +#include "packager/media/formats/mp2t/mp2t_common.h" + +namespace edash_packager { +namespace media { +namespace mp2t { + +namespace { + +H26xByteToUnitStreamConverter* CreateStreamConverter(Nalu::CodecType type) { + if (type == Nalu::kH264) { + return new H264ByteToUnitStreamConverter(); + } else { + DCHECK_EQ(Nalu::kH265, type); + return new H265ByteToUnitStreamConverter(); + } +} + +} // anonymous namespace + +EsParserH26x::EsParserH26x(Nalu::CodecType type, + uint32_t pid, + const EmitSampleCB& emit_sample_cb) + : EsParser(pid), + emit_sample_cb_(emit_sample_cb), + type_(type), + es_queue_(new media::OffsetByteQueue()), + current_access_unit_pos_(0), + found_access_unit_(false), + stream_converter_(CreateStreamConverter(type)), + pending_sample_duration_(0), + waiting_for_key_frame_(true) { +} + +EsParserH26x::~EsParserH26x() {} + +bool EsParserH26x::Parse(const uint8_t* buf, + int size, + int64_t pts, + int64_t dts) { + // Note: Parse is invoked each time a PES packet has been reassembled. + // Unfortunately, a PES packet does not necessarily map + // to an h264/h265 access unit, although the HLS recommendation is to use one + // PES for each access unit (but this is just a recommendation and some + // streams do not comply with this recommendation). + + // HLS recommendation: "In AVC video, you should have both a DTS and a + // PTS in each PES header". + // However, some streams do not comply with this recommendation. + DVLOG_IF(1, pts == kNoTimestamp) << "Each video PES should have a PTS"; + if (pts != kNoTimestamp) { + TimingDesc timing_desc; + timing_desc.pts = pts; + timing_desc.dts = (dts != kNoTimestamp) ? dts : pts; + + // Link the end of the byte queue with the incoming timing descriptor. + timing_desc_list_.push_back( + std::pair(es_queue_->tail(), timing_desc)); + } + + // Add the incoming bytes to the ES queue. + es_queue_->Push(buf, size); + + // Skip to the first access unit. + if (!found_access_unit_) { + if (!FindNextAccessUnit(current_access_unit_pos_, + ¤t_access_unit_pos_)) { + return true; + } + es_queue_->Trim(current_access_unit_pos_); + found_access_unit_ = true; + } + + return ParseInternal(); +} + +void EsParserH26x::Flush() { + DVLOG(1) << "EsParserH26x::Flush"; + + // Simulate an additional AUD to force emitting the last access unit + // which is assumed to be complete at this point. + if (type_ == Nalu::kH264) { + uint8_t aud[] = {0x00, 0x00, 0x01, 0x09}; + es_queue_->Push(aud, sizeof(aud)); + } else { + DCHECK_EQ(Nalu::kH265, type_); + uint8_t aud[] = {0x00, 0x00, 0x01, 0x46, 0x01}; + es_queue_->Push(aud, sizeof(aud)); + } + ParseInternal(); + + if (pending_sample_) { + // Flush pending sample. + DCHECK(pending_sample_duration_); + pending_sample_->set_duration(pending_sample_duration_); + emit_sample_cb_.Run(pid(), pending_sample_); + pending_sample_ = scoped_refptr(); + } +} + +void EsParserH26x::Reset() { + es_queue_.reset(new media::OffsetByteQueue()); + current_access_unit_pos_ = 0; + found_access_unit_ = false; + timing_desc_list_.clear(); + pending_sample_ = scoped_refptr(); + pending_sample_duration_ = 0; + waiting_for_key_frame_ = true; +} + +bool EsParserH26x::FindNextAccessUnit(int64_t stream_pos, + int64_t* next_unit_pos) { + // TODO(modmaker): Avoid re-parsing by saving old position. + // Every access unit must have a VCL entry and defines the end of the access + // unit. Track it to return on the element after it so we get the whole + // access unit. + bool seen_vcl_nalu = false; + while (true) { + const uint8_t* es; + int size; + es_queue_->PeekAt(stream_pos, &es, &size); + + // Find a start code. + uint64_t start_code_offset; + uint8_t start_code_size; + bool start_code_found = NaluReader::FindStartCode( + es, size, &start_code_offset, &start_code_size); + stream_pos += start_code_offset; + + // No start code found or NALU type not available yet. + if (!start_code_found || + start_code_offset + start_code_size >= static_cast(size)) { + return false; + } + + Nalu nalu; + const uint8_t* nalu_ptr = es + start_code_offset + start_code_size; + size_t nalu_size = size - (start_code_offset + start_code_size); + if (nalu.Initialize(type_, nalu_ptr, nalu_size)) { + // ITU H.264 sec. 7.4.1.2.3 + // H264: The first of the NAL units with |can_start_access_unit() == true| + // after the last VCL NAL unit of a primary coded picture specifies the + // start of a new access unit. |nuh_layer_id()| is for H265 only; it is + // included below for ease of computation (the value is always 0). + // ITU H.265 sec. 7.4.2.4.4 + // H265: The first of the NAL units with |can_start_access_unit() == true| + // after the last VCL NAL unit preceding firstBlPicNalUnit (the first + // VCL NAL unit of a coded picture with nuh_layer_id equal to 0), if + // any, specifies the start of a new access unit. + // TODO(modmaker): This does not handle nuh_layer_id != 0 correctly. + // AUD VCL SEI VCL* VPS VCL + // | Current method splits here. + // | Should split here. + // If we are searching for the first access unit, then stop at the first + // NAL unit that can start an access unit. + if ((seen_vcl_nalu || !found_access_unit_) && + nalu.can_start_access_unit()) { + break; + } + bool is_vcl_nalu = nalu.is_video_slice() && nalu.nuh_layer_id() == 0; + seen_vcl_nalu |= is_vcl_nalu; + } + + // The current NALU is not an AUD, skip the start code + // and continue parsing the stream. + stream_pos += start_code_size; + } + + *next_unit_pos = stream_pos; + return true; +} + +bool EsParserH26x::ParseInternal() { + DCHECK_LE(es_queue_->head(), current_access_unit_pos_); + DCHECK_LE(current_access_unit_pos_, es_queue_->tail()); + + // Resume parsing later if no AUD was found. + int64_t access_unit_end; + if (!FindNextAccessUnit(current_access_unit_pos_, &access_unit_end)) + return true; + + // At this point, we know we have a full access unit. + bool is_key_frame = false; + int pps_id_for_access_unit = -1; + + const uint8_t* es; + int size; + es_queue_->PeekAt(current_access_unit_pos_, &es, &size); + int access_unit_size = base::checked_cast( + access_unit_end - current_access_unit_pos_); + DCHECK_LE(access_unit_size, size); + NaluReader reader(type_, kIsAnnexbByteStream, es, access_unit_size); + + // TODO(modmaker): Consider combining with FindNextAccessUnit to avoid + // scanning the data twice. + while (true) { + Nalu nalu; + bool is_eos = false; + switch (reader.Advance(&nalu)) { + case NaluReader::kOk: + break; + case NaluReader::kEOStream: + is_eos = true; + break; + default: + return false; + } + if (is_eos) + break; + + if (!ProcessNalu(nalu, &is_key_frame, &pps_id_for_access_unit)) + return false; + } + + if (waiting_for_key_frame_) { + waiting_for_key_frame_ = !is_key_frame; + } + if (!waiting_for_key_frame_) { + // Emit a frame and move the stream to the next AUD position. + RCHECK(EmitFrame(current_access_unit_pos_, access_unit_size, + is_key_frame, pps_id_for_access_unit)); + } + current_access_unit_pos_ = access_unit_end; + es_queue_->Trim(current_access_unit_pos_); + + return true; +} + +bool EsParserH26x::EmitFrame(int64_t access_unit_pos, + int access_unit_size, + bool is_key_frame, + int pps_id) { + // Get the access unit timing info. + TimingDesc current_timing_desc = {kNoTimestamp, kNoTimestamp}; + while (!timing_desc_list_.empty() && + timing_desc_list_.front().first <= access_unit_pos) { + current_timing_desc = timing_desc_list_.front().second; + timing_desc_list_.pop_front(); + } + if (current_timing_desc.pts == kNoTimestamp) + return false; + + // Emit a frame. + DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_ + << " size=" << access_unit_size; + int es_size; + const uint8_t* es; + es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size); + CHECK_GE(es_size, access_unit_size); + + // Convert frame to unit stream format. + std::vector converted_frame; + if (!stream_converter_->ConvertByteStreamToNalUnitStream( + es, access_unit_size, &converted_frame)) { + DLOG(ERROR) << "Failure to convert video frame to unit stream format."; + return false; + } + + // Update the video decoder configuration if needed. + RCHECK(UpdateVideoDecoderConfig(pps_id)); + + // Create the media sample, emitting always the previous sample after + // calculating its duration. + scoped_refptr media_sample = MediaSample::CopyFrom( + converted_frame.data(), converted_frame.size(), is_key_frame); + media_sample->set_dts(current_timing_desc.dts); + media_sample->set_pts(current_timing_desc.pts); + if (pending_sample_) { + DCHECK_GT(media_sample->dts(), pending_sample_->dts()); + pending_sample_duration_ = media_sample->dts() - pending_sample_->dts(); + pending_sample_->set_duration(pending_sample_duration_); + emit_sample_cb_.Run(pid(), pending_sample_); + } + pending_sample_ = media_sample; + + return true; +} + +} // namespace mp2t +} // namespace media +} // namespace edash_packager diff --git a/packager/media/formats/mp2t/es_parser_h26x.h b/packager/media/formats/mp2t/es_parser_h26x.h new file mode 100644 index 0000000000..4edff45d33 --- /dev/null +++ b/packager/media/formats/mp2t/es_parser_h26x.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef MEDIA_FORMATS_MP2T_ES_PARSER_H26x_H_ +#define MEDIA_FORMATS_MP2T_ES_PARSER_H26x_H_ + +#include + +#include + +#include "packager/base/callback.h" +#include "packager/base/compiler_specific.h" +#include "packager/base/memory/scoped_ptr.h" +#include "packager/media/filters/nalu_reader.h" +#include "packager/media/formats/mp2t/es_parser.h" + +namespace edash_packager { +namespace media { + +class H26xByteToUnitStreamConverter; +class OffsetByteQueue; + +namespace mp2t { + +// A base class for common code between the H.264/H.265 es parsers. +class EsParserH26x : public EsParser { + public: + EsParserH26x(Nalu::CodecType type, + uint32_t pid, + const EmitSampleCB& emit_sample_cb); + ~EsParserH26x() override; + + // EsParser implementation overrides. + bool Parse(const uint8_t* buf, int size, int64_t pts, int64_t dts) override; + void Flush() override; + void Reset() override; + + protected: + const H26xByteToUnitStreamConverter* stream_converter() const { + return stream_converter_.get(); + } + + private: + struct TimingDesc { + int64_t dts; + int64_t pts; + }; + + // Processes a NAL unit found in ParseInternal. The @a pps_id_for_access_unit + // value will be passed to UpdateVideoDecoderConfig. + virtual bool ProcessNalu(const Nalu& nalu, + bool* is_key_frame, + int* pps_id_for_access_unit) = 0; + + // Update the video decoder config. + // Return true if successful. + virtual bool UpdateVideoDecoderConfig(int pps_id) = 0; + + // Find the start of the next access unit staring at |stream_pos|. + // Return true if the end is found. + // If found, |*next_unit_start| contains the start of the next access unit. + // Otherwise, |*next_unit_start| is unchanged. + bool FindNextAccessUnit(int64_t stream_pos, int64_t* next_unit_start); + + // Resumes the H264 ES parsing. + // Return true if successful. + bool ParseInternal(); + + // Emit a frame whose position in the ES queue starts at |access_unit_pos|. + // Returns true if successful, false if no PTS is available for the frame. + bool EmitFrame(int64_t access_unit_pos, + int access_unit_size, + bool is_key_frame, + int pps_id); + + // Callback to pass the frames. + EmitSampleCB emit_sample_cb_; + + // The type of stream being parsed. + Nalu::CodecType type_; + + // Bytes of the ES stream that have not been emitted yet. + scoped_ptr es_queue_; + std::list> timing_desc_list_; + + // Parser state. + // - |current_access_unit_pos_| is pointing to an annexB syncword + // representing the first NALU of an access unit. + int64_t current_access_unit_pos_; + bool found_access_unit_; + + // Filter to convert H.264/H.265 Annex B byte stream to unit stream. + scoped_ptr stream_converter_; + + // Frame for which we do not yet have a duration. + scoped_refptr pending_sample_; + uint64_t pending_sample_duration_; + + // Indicates whether waiting for first key frame. + bool waiting_for_key_frame_; +}; + +} // namespace mp2t +} // namespace media +} // namespace edash_packager + +#endif diff --git a/packager/media/formats/mp2t/mp2t.gyp b/packager/media/formats/mp2t/mp2t.gyp index 7c1fb57e6e..9a4df07541 100644 --- a/packager/media/formats/mp2t/mp2t.gyp +++ b/packager/media/formats/mp2t/mp2t.gyp @@ -19,6 +19,10 @@ 'es_parser_adts.h', 'es_parser_h264.cc', 'es_parser_h264.h', + 'es_parser_h265.cc', + 'es_parser_h265.h', + 'es_parser_h26x.cc', + 'es_parser_h26x.h', 'es_parser.h', 'mp2t_media_parser.cc', 'mp2t_media_parser.h', @@ -45,6 +49,7 @@ ], 'dependencies': [ '../../base/media_base.gyp:media_base', + '../../filters/filters.gyp:filters', # TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS. # Break this dependency on mp4 by moving it to media/filters. '../../formats/mp4/mp4.gyp:mp4', diff --git a/packager/media/formats/mp2t/mp2t_media_parser.cc b/packager/media/formats/mp2t/mp2t_media_parser.cc index b2c76002bc..60bae5734d 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser.cc +++ b/packager/media/formats/mp2t/mp2t_media_parser.cc @@ -12,6 +12,7 @@ #include "packager/media/formats/mp2t/es_parser.h" #include "packager/media/formats/mp2t/es_parser_adts.h" #include "packager/media/formats/mp2t/es_parser_h264.h" +#include "packager/media/formats/mp2t/es_parser_h265.h" #include "packager/media/formats/mp2t/mp2t_common.h" #include "packager/media/formats/mp2t/ts_packet.h" #include "packager/media/formats/mp2t/ts_section.h" @@ -28,6 +29,7 @@ enum StreamType { kStreamTypeMpeg1Audio = 0x3, kStreamTypeAAC = 0xf, kStreamTypeAVC = 0x1b, + kStreamTypeHEVC = 0x24, }; class PidState { @@ -300,6 +302,14 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid, base::Unretained(this)), base::Bind(&Mp2tMediaParser::OnEmitSample, base::Unretained(this)))); + } else if (stream_type == kStreamTypeHEVC) { + es_parser.reset( + new EsParserH265( + pes_pid, + base::Bind(&Mp2tMediaParser::OnNewStreamInfo, + base::Unretained(this)), + base::Bind(&Mp2tMediaParser::OnEmitSample, + base::Unretained(this)))); } else if (stream_type == kStreamTypeAAC) { es_parser.reset( new EsParserAdts( diff --git a/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc b/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc index 79daf6c104..229bb6f920 100644 --- a/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc +++ b/packager/media/formats/mp2t/mp2t_media_parser_unittest.cc @@ -73,15 +73,12 @@ class Mp2tMediaParserTest : public testing::Test { bool OnNewSample(uint32_t track_id, const scoped_refptr& sample) { - std::string stream_type; StreamMap::const_iterator stream = stream_map_.find(track_id); if (stream != stream_map_.end()) { if (stream->second->stream_type() == kStreamAudio) { ++audio_frame_count_; - stream_type = "audio"; } else if (stream->second->stream_type() == kStreamVideo) { ++video_frame_count_; - stream_type = "video"; if (video_min_dts_ == kNoTimestamp) video_min_dts_ = sample->dts(); // Verify timestamps are increasing. @@ -121,20 +118,36 @@ class Mp2tMediaParserTest : public testing::Test { } }; -TEST_F(Mp2tMediaParserTest, UnalignedAppend17) { +TEST_F(Mp2tMediaParserTest, UnalignedAppend17_H264) { // Test small, non-segment-aligned appends. ParseMpeg2TsFile("bear-640x360.ts", 17); - EXPECT_EQ(video_frame_count_, 79); + EXPECT_EQ(79, video_frame_count_); EXPECT_TRUE(parser_->Flush()); - EXPECT_EQ(video_frame_count_, 82); + EXPECT_EQ(82, video_frame_count_); } -TEST_F(Mp2tMediaParserTest, UnalignedAppend512) { +TEST_F(Mp2tMediaParserTest, UnalignedAppend512_H264) { // Test small, non-segment-aligned appends. ParseMpeg2TsFile("bear-640x360.ts", 512); - EXPECT_EQ(video_frame_count_, 79); + EXPECT_EQ(79, video_frame_count_); EXPECT_TRUE(parser_->Flush()); - EXPECT_EQ(video_frame_count_, 82); + EXPECT_EQ(82, video_frame_count_); +} + +TEST_F(Mp2tMediaParserTest, UnalignedAppend17_H265) { + // Test small, non-segment-aligned appends. + ParseMpeg2TsFile("bear-640x360-hevc.ts", 17); + EXPECT_EQ(79, video_frame_count_); + EXPECT_TRUE(parser_->Flush()); + EXPECT_EQ(82, video_frame_count_); +} + +TEST_F(Mp2tMediaParserTest, UnalignedAppend512_H265) { + // Test small, non-segment-aligned appends. + ParseMpeg2TsFile("bear-640x360-hevc.ts", 512); + EXPECT_EQ(79, video_frame_count_); + EXPECT_TRUE(parser_->Flush()); + EXPECT_EQ(82, video_frame_count_); } TEST_F(Mp2tMediaParserTest, TimestampWrapAround) { @@ -143,7 +156,7 @@ TEST_F(Mp2tMediaParserTest, TimestampWrapAround) { // wrap around in the Mpeg2 TS stream. ParseMpeg2TsFile("bear-640x360_ptswraparound.ts", 512); EXPECT_TRUE(parser_->Flush()); - EXPECT_EQ(video_frame_count_, 82); + EXPECT_EQ(82, video_frame_count_); EXPECT_LT(video_min_dts_, static_cast(1) << 33); EXPECT_GT(video_max_dts_, static_cast(1) << 33); } diff --git a/packager/media/test/data/README b/packager/media/test/data/README index 1d35ae6a6c..3e3a474b03 100644 --- a/packager/media/test/data/README +++ b/packager/media/test/data/README @@ -29,6 +29,7 @@ vorbis-packet-3 - timestamp: 2902ms, duration: 0ms bear-640x360.ts - AVC + AAC encode, multiplexed into an MPEG2-TS container. bear-640x360_ptswraparound.ts - Same as bear-640x360.ts, with a timestamp wrap-around in the middle, created with the below command: ffmpeg -itsoffset 95442 -i bear-640x360.ts -c:v copy -c:a copy -muxdelay 0 bear-640x360_ptswraparound.ts +bear-640x360-hevc.ts - HEVC + AAC encode, multiplexed into an MPEG2-TS container. // ISO-BMFF streams. bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container. diff --git a/packager/media/test/data/bear-640x360-hevc.ts b/packager/media/test/data/bear-640x360-hevc.ts new file mode 100644 index 0000000000..14ad463f71 Binary files /dev/null and b/packager/media/test/data/bear-640x360-hevc.ts differ