Add support for H.265 in MPEG2-TS demuxer.

This also changes the H.264 parser to correctly determine access
units.

Closes #46
Issue #47

Change-Id: I69a8c47ebf4fe35cef0592997460158b3131084e
This commit is contained in:
Jacob Trimble 2016-03-31 12:48:16 -07:00
parent 2abc7c60b7
commit 86369efc30
14 changed files with 771 additions and 348 deletions

View File

@ -9,7 +9,7 @@ Current supported codecs:
| Codecs | ISO-BMFF | WebM | MPEG2-TS | WVM | | Codecs | ISO-BMFF | WebM | MPEG2-TS | WVM |
|:-----------------:|:------------:|:------------:|:------------:|:-----------:| |:-----------------:|:------------:|:------------:|:------------:|:-----------:|
| H264 (AVC) | I / O | - | I | I | | H264 (AVC) | I / O | - | I | I |
| H265 (HEVC) | I / O | - | - | - | | H265 (HEVC) | I / O | - | I | - |
| VP8 | I / O | I / O | - | - | | VP8 | I / O | I / O | - | - |
| VP9 | I / O | I / O | - | - | | VP9 | I / O | I / O | - | - |
| AAC | I / O | - | I | I | | AAC | I / O | - | I | I |

View File

@ -88,6 +88,10 @@ bool Nalu::InitializeFromH264(const uint8_t* data, uint64_t size) {
is_video_slice_ = (type_ >= Nalu::H264_NonIDRSlice && is_video_slice_ = (type_ >= Nalu::H264_NonIDRSlice &&
type_ <= Nalu::H264_IDRSlice); 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; 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; 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; return true;
} }

View File

@ -36,7 +36,9 @@ class Nalu {
H264_EOSeq = 10, H264_EOSeq = 10,
H264_FillerData = 12, H264_FillerData = 12,
H264_SPSExtension = 13, H264_SPSExtension = 13,
H264_PrefixNALUnit = 14,
H264_SubsetSPS = 15, H264_SubsetSPS = 15,
H264_DepthParameterSet = 16,
H264_Reserved17 = 17, H264_Reserved17 = 17,
H264_Reserved18 = 18, H264_Reserved18 = 18,
H264_CodedSliceExtension = 20, H264_CodedSliceExtension = 20,
@ -70,7 +72,12 @@ class Nalu {
H265_EOS = 36, H265_EOS = 36,
H265_EOB = 37, H265_EOB = 37,
H265_PREFIX_SEI = 39,
H265_RSV_NVCL41 = 41, H265_RSV_NVCL41 = 41,
H265_RSV_NVCL44 = 44,
H265_UNSPEC48 = 48,
H265_UNSPEC55 = 55,
}; };
enum CodecType { enum CodecType {
kH264, kH264,
@ -96,6 +103,7 @@ class Nalu {
int type() const { return type_; } int type() const { return type_; }
bool is_video_slice() const { return is_video_slice_; } bool is_video_slice() const { return is_video_slice_; }
bool can_start_access_unit() const { return can_start_access_unit_; }
private: private:
bool InitializeFromH264(const uint8_t* data, uint64_t size); bool InitializeFromH264(const uint8_t* data, uint64_t size);
@ -115,6 +123,7 @@ class Nalu {
int nuh_temporal_id_; int nuh_temporal_id_;
int type_; int type_;
bool is_video_slice_; bool is_video_slice_;
bool can_start_access_unit_;
// Don't use DISALLOW_COPY_AND_ASSIGN since it is just numbers and a pointer // 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. // it does not own. This allows Nalus to be stored in a vector.

View File

@ -7,9 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include "packager/base/logging.h" #include "packager/base/logging.h"
#include "packager/base/numerics/safe_conversions.h"
#include "packager/media/base/media_sample.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/timestamp.h"
#include "packager/media/base/video_stream_info.h" #include "packager/media/base/video_stream_info.h"
#include "packager/media/filters/avc_decoder_configuration.h" #include "packager/media/filters/avc_decoder_configuration.h"
@ -21,314 +19,98 @@ namespace edash_packager {
namespace media { namespace media {
namespace mp2t { 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, EsParserH264::EsParserH264(uint32_t pid,
const NewStreamInfoCB& new_stream_info_cb, const NewStreamInfoCB& new_stream_info_cb,
const EmitSampleCB& emit_sample_cb) const EmitSampleCB& emit_sample_cb)
: EsParser(pid), : EsParserH26x(Nalu::kH264, pid, emit_sample_cb),
new_stream_info_cb_(new_stream_info_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), decoder_config_check_pending_(false),
pending_sample_duration_(0), h264_parser_(new H264Parser()) {}
waiting_for_key_frame_(true) {
}
EsParserH264::~EsParserH264() { 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<int64_t, TimingDesc>(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(&current_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<MediaSample>();
}
}
void EsParserH264::Reset() { void EsParserH264::Reset() {
DVLOG(1) << "EsParserH264::Reset"; DVLOG(1) << "EsParserH264::Reset";
es_queue_.reset(new media::OffsetByteQueue());
h264_parser_.reset(new H264Parser()); 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<StreamInfo>(); last_video_decoder_config_ = scoped_refptr<StreamInfo>();
decoder_config_check_pending_ = false; decoder_config_check_pending_ = false;
pending_sample_ = scoped_refptr<MediaSample>(); EsParserH26x::Reset();
pending_sample_duration_ = 0;
waiting_for_key_frame_ = true;
} }
bool EsParserH264::FindAUD(int64_t* stream_pos) { bool EsParserH264::ProcessNalu(const Nalu& nalu,
while (true) { bool* is_key_frame,
const uint8_t* es; int* pps_id_for_access_unit) {
int size; switch (nalu.type()) {
es_queue_->PeekAt(*stream_pos, &es, &size); case Nalu::H264_AUD: {
DVLOG(LOG_LEVEL_ES) << "Nalu: AUD";
// 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<uint64_t>(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)
break; break;
}
// The current NALU is not an AUD, skip the start code case Nalu::H264_SPS: {
// and continue parsing the stream. DVLOG(LOG_LEVEL_ES) << "Nalu: SPS";
*stream_pos += start_code_size; int sps_id;
} if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk)
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(&current_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<int, int64_t>(
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:
return false; return false;
} decoder_config_check_pending_ = true;
if (is_eos)
break; break;
}
switch (nalu.type()) { case Nalu::H264_PPS: {
case Nalu::H264_AUD: { DVLOG(LOG_LEVEL_ES) << "Nalu: PPS";
DVLOG(LOG_LEVEL_ES) << "Nalu: AUD"; int pps_id;
break; if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) {
} // Allow PPS parsing to fail if waiting for SPS.
case Nalu::H264_SPS: { if (last_video_decoder_config_)
DVLOG(LOG_LEVEL_ES) << "Nalu: SPS";
int sps_id;
if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk)
return false; return false;
} else {
decoder_config_check_pending_ = true; decoder_config_check_pending_ = true;
break;
} }
case Nalu::H264_PPS: { break;
DVLOG(LOG_LEVEL_ES) << "Nalu: PPS"; }
int pps_id; case Nalu::H264_IDRSlice:
if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) { case Nalu::H264_NonIDRSlice: {
// Allow PPS parsing to fail if waiting for SPS. *is_key_frame = (nalu.type() == Nalu::H264_IDRSlice);
if (last_video_decoder_config_) DVLOG(LOG_LEVEL_ES) << "Nalu: slice IDR=" << is_key_frame;
return false; H264SliceHeader shdr;
} else { if (h264_parser_->ParseSliceHeader(nalu, &shdr) != H264Parser::kOk) {
decoder_config_check_pending_ = true; // Only accept an invalid SPS/PPS at the beginning when the stream
} // does not necessarily start with an SPS/PPS/IDR.
break; if (last_video_decoder_config_)
} return false;
case Nalu::H264_IDRSlice: } else {
case Nalu::H264_NonIDRSlice: { *pps_id_for_access_unit = shdr.pic_parameter_set_id;
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;
}
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; return true;
} }
bool EsParserH264::EmitFrame(int64_t access_unit_pos, bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) {
int access_unit_size, // Update the video decoder configuration if needed.
bool is_key_frame, if (!decoder_config_check_pending_)
int pps_id) { return true;
// 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. const H264Pps* pps = h264_parser_->GetPps(pps_id);
DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_ const H264Sps* sps;
<< " size=" << access_unit_size; if (!pps) {
int es_size; // Only accept an invalid PPS at the beginning when the stream
const uint8_t* es; // does not necessarily start with an SPS/PPS/IDR.
es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size); // In this case, the initial frames are conveyed to the upper layer with
CHECK_GE(es_size, access_unit_size); // an invalid VideoDecoderConfig and it's up to the upper layer
// to process this kind of frame accordingly.
// Convert frame to unit stream format. return last_video_decoder_config_ == nullptr;
std::vector<uint8_t> converted_frame; } else {
if (!stream_converter_->ConvertByteStreamToNalUnitStream( sps = h264_parser_->GetSps(pps->seq_parameter_set_id);
es, access_unit_size, &converted_frame)) { if (!sps)
DLOG(ERROR) << "Failure to convert video frame to unit stream format."; return false;
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<MediaSample> 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<uint8_t> decoder_config_record; std::vector<uint8_t> decoder_config_record;
if (!stream_converter_->GetDecoderConfigurationRecord( if (!stream_converter()->GetDecoderConfigurationRecord(
&decoder_config_record)) { &decoder_config_record)) {
DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord"; DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord";
return false; return false;

View File

@ -7,21 +7,14 @@
#include <stdint.h> #include <stdint.h>
#include <list>
#include <utility>
#include "packager/base/callback.h" #include "packager/base/callback.h"
#include "packager/base/compiler_specific.h"
#include "packager/base/memory/scoped_ptr.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 edash_packager {
namespace media { namespace media {
class H264ByteToUnitStreamConverter;
class H264Parser; class H264Parser;
class OffsetByteQueue;
struct H264Sps;
namespace mp2t { 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" // 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;" // "Each AVC access unit shall contain an access unit delimiter NAL Unit;"
// //
class EsParserH264 : public EsParser { class EsParserH264 : public EsParserH26x {
public: public:
EsParserH264(uint32_t pid, EsParserH264(uint32_t pid,
const NewStreamInfoCB& new_stream_info_cb, const NewStreamInfoCB& new_stream_info_cb,
const EmitSampleCB& emit_sample_cb); const EmitSampleCB& emit_sample_cb);
~EsParserH264() override; ~EsParserH264() override;
// EsParser implementation overrides. // EsParserH26x implementation override.
bool Parse(const uint8_t* buf, int size, int64_t pts, int64_t dts) override;
void Flush() override;
void Reset() override; void Reset() override;
private: private:
struct TimingDesc { // Processes a NAL unit found in ParseInternal. The @a pps_id_for_access_unit
int64_t dts; // value will be passed to UpdateVideoDecoderConfig.
int64_t pts; bool ProcessNalu(const Nalu& nalu,
}; bool* is_key_frame,
int* pps_id_for_access_unit) override;
// 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);
// Update the video decoder config based on an H264 SPS. // Update the video decoder config based on an H264 SPS.
// Return true if successful. // 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_; NewStreamInfoCB new_stream_info_cb_;
EmitSampleCB emit_sample_cb_;
// Bytes of the ES stream that have not been emitted yet.
scoped_ptr<media::OffsetByteQueue> es_queue_;
std::list<std::pair<int64_t, TimingDesc> > 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<H264Parser> 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<H264ByteToUnitStreamConverter> stream_converter_;
// Last video decoder config.
scoped_refptr<StreamInfo> last_video_decoder_config_; scoped_refptr<StreamInfo> last_video_decoder_config_;
bool decoder_config_check_pending_; bool decoder_config_check_pending_;
// Frame for which we do not yet have a duration. scoped_ptr<H264Parser> h264_parser_;
scoped_refptr<MediaSample> pending_sample_;
uint64_t pending_sample_duration_;
// Indicates whether waiting for first key frame.
bool waiting_for_key_frame_;
}; };
} // namespace mp2t } // namespace mp2t

View File

@ -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 <stdint.h>
#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<StreamInfo>();
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<uint8_t> 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<StreamInfo>(
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

View File

@ -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 <stdint.h>
#include <list>
#include <utility>
#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<StreamInfo> last_video_decoder_config_;
bool decoder_config_check_pending_;
scoped_ptr<H265Parser> h265_parser_;
};
} // namespace mp2t
} // namespace media
} // namespace edash_packager
#endif // MEDIA_FORMATS_MP2T_ES_PARSER_H265_H_

View File

@ -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 <stdint.h>
#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<int64_t, TimingDesc>(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_,
&current_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<MediaSample>();
}
}
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<MediaSample>();
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<uint64_t>(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<int, int64_t>(
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<uint8_t> 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<MediaSample> 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

View File

@ -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 <stdint.h>
#include <list>
#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<media::OffsetByteQueue> es_queue_;
std::list<std::pair<int64_t, TimingDesc>> 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<H26xByteToUnitStreamConverter> stream_converter_;
// Frame for which we do not yet have a duration.
scoped_refptr<MediaSample> 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

View File

@ -19,6 +19,10 @@
'es_parser_adts.h', 'es_parser_adts.h',
'es_parser_h264.cc', 'es_parser_h264.cc',
'es_parser_h264.h', 'es_parser_h264.h',
'es_parser_h265.cc',
'es_parser_h265.h',
'es_parser_h26x.cc',
'es_parser_h26x.h',
'es_parser.h', 'es_parser.h',
'mp2t_media_parser.cc', 'mp2t_media_parser.cc',
'mp2t_media_parser.h', 'mp2t_media_parser.h',
@ -45,6 +49,7 @@
], ],
'dependencies': [ 'dependencies': [
'../../base/media_base.gyp:media_base', '../../base/media_base.gyp:media_base',
'../../filters/filters.gyp:filters',
# TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS. # TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS.
# Break this dependency on mp4 by moving it to media/filters. # Break this dependency on mp4 by moving it to media/filters.
'../../formats/mp4/mp4.gyp:mp4', '../../formats/mp4/mp4.gyp:mp4',

View File

@ -12,6 +12,7 @@
#include "packager/media/formats/mp2t/es_parser.h" #include "packager/media/formats/mp2t/es_parser.h"
#include "packager/media/formats/mp2t/es_parser_adts.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_h264.h"
#include "packager/media/formats/mp2t/es_parser_h265.h"
#include "packager/media/formats/mp2t/mp2t_common.h" #include "packager/media/formats/mp2t/mp2t_common.h"
#include "packager/media/formats/mp2t/ts_packet.h" #include "packager/media/formats/mp2t/ts_packet.h"
#include "packager/media/formats/mp2t/ts_section.h" #include "packager/media/formats/mp2t/ts_section.h"
@ -28,6 +29,7 @@ enum StreamType {
kStreamTypeMpeg1Audio = 0x3, kStreamTypeMpeg1Audio = 0x3,
kStreamTypeAAC = 0xf, kStreamTypeAAC = 0xf,
kStreamTypeAVC = 0x1b, kStreamTypeAVC = 0x1b,
kStreamTypeHEVC = 0x24,
}; };
class PidState { class PidState {
@ -300,6 +302,14 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid,
base::Unretained(this)), base::Unretained(this)),
base::Bind(&Mp2tMediaParser::OnEmitSample, base::Bind(&Mp2tMediaParser::OnEmitSample,
base::Unretained(this)))); 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) { } else if (stream_type == kStreamTypeAAC) {
es_parser.reset( es_parser.reset(
new EsParserAdts( new EsParserAdts(

View File

@ -73,15 +73,12 @@ class Mp2tMediaParserTest : public testing::Test {
bool OnNewSample(uint32_t track_id, bool OnNewSample(uint32_t track_id,
const scoped_refptr<MediaSample>& sample) { const scoped_refptr<MediaSample>& sample) {
std::string stream_type;
StreamMap::const_iterator stream = stream_map_.find(track_id); StreamMap::const_iterator stream = stream_map_.find(track_id);
if (stream != stream_map_.end()) { if (stream != stream_map_.end()) {
if (stream->second->stream_type() == kStreamAudio) { if (stream->second->stream_type() == kStreamAudio) {
++audio_frame_count_; ++audio_frame_count_;
stream_type = "audio";
} else if (stream->second->stream_type() == kStreamVideo) { } else if (stream->second->stream_type() == kStreamVideo) {
++video_frame_count_; ++video_frame_count_;
stream_type = "video";
if (video_min_dts_ == kNoTimestamp) if (video_min_dts_ == kNoTimestamp)
video_min_dts_ = sample->dts(); video_min_dts_ = sample->dts();
// Verify timestamps are increasing. // 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. // Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-640x360.ts", 17); ParseMpeg2TsFile("bear-640x360.ts", 17);
EXPECT_EQ(video_frame_count_, 79); EXPECT_EQ(79, video_frame_count_);
EXPECT_TRUE(parser_->Flush()); 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. // Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-640x360.ts", 512); ParseMpeg2TsFile("bear-640x360.ts", 512);
EXPECT_EQ(video_frame_count_, 79); EXPECT_EQ(79, video_frame_count_);
EXPECT_TRUE(parser_->Flush()); 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) { TEST_F(Mp2tMediaParserTest, TimestampWrapAround) {
@ -143,7 +156,7 @@ TEST_F(Mp2tMediaParserTest, TimestampWrapAround) {
// wrap around in the Mpeg2 TS stream. // wrap around in the Mpeg2 TS stream.
ParseMpeg2TsFile("bear-640x360_ptswraparound.ts", 512); ParseMpeg2TsFile("bear-640x360_ptswraparound.ts", 512);
EXPECT_TRUE(parser_->Flush()); EXPECT_TRUE(parser_->Flush());
EXPECT_EQ(video_frame_count_, 82); EXPECT_EQ(82, video_frame_count_);
EXPECT_LT(video_min_dts_, static_cast<int64_t>(1) << 33); EXPECT_LT(video_min_dts_, static_cast<int64_t>(1) << 33);
EXPECT_GT(video_max_dts_, static_cast<int64_t>(1) << 33); EXPECT_GT(video_max_dts_, static_cast<int64_t>(1) << 33);
} }

View File

@ -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.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: 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 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. // ISO-BMFF streams.
bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container. bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container.

Binary file not shown.