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:
parent
2abc7c60b7
commit
86369efc30
|
@ -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 |
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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(¤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<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(¤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<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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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_,
|
||||||
|
¤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<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
|
|
@ -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
|
|
@ -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',
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
Loading…
Reference in New Issue