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 |
|
||||
|:-----------------:|:------------:|:------------:|:------------:|:-----------:|
|
||||
| H264 (AVC) | I / O | - | I | I |
|
||||
| H265 (HEVC) | I / O | - | - | - |
|
||||
| H265 (HEVC) | I / O | - | I | - |
|
||||
| VP8 | I / O | I / O | - | - |
|
||||
| VP9 | I / O | I / O | - | - |
|
||||
| AAC | I / O | - | I | I |
|
||||
|
|
|
@ -88,6 +88,10 @@ bool Nalu::InitializeFromH264(const uint8_t* data, uint64_t size) {
|
|||
|
||||
is_video_slice_ = (type_ >= Nalu::H264_NonIDRSlice &&
|
||||
type_ <= Nalu::H264_IDRSlice);
|
||||
can_start_access_unit_ =
|
||||
(is_video_slice_ || type_ == Nalu::H264_AUD || type_ == Nalu::H264_SPS ||
|
||||
type_ == Nalu::H264_PPS || type_ == Nalu::H264_SEIMessage ||
|
||||
(type_ >= Nalu::H264_PrefixNALUnit && type_ <= Nalu::H264_Reserved18));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -150,6 +154,13 @@ bool Nalu::InitializeFromH265(const uint8_t* data, uint64_t size) {
|
|||
}
|
||||
|
||||
is_video_slice_ = type_ >= Nalu::H265_TRAIL_N && type_ <= Nalu::H265_CRA_NUT;
|
||||
can_start_access_unit_ =
|
||||
nuh_layer_id_ == 0 &&
|
||||
(is_video_slice_ || type_ == Nalu::H265_AUD || type_ == Nalu::H265_VPS ||
|
||||
type_ == Nalu::H265_SPS || type_ == Nalu::H265_PPS ||
|
||||
type_ == Nalu::H265_PREFIX_SEI ||
|
||||
(type_ >= Nalu::H265_RSV_NVCL41 && type_ <= Nalu::H265_RSV_NVCL44) ||
|
||||
(type_ >= Nalu::H265_UNSPEC48 && type_ <= Nalu::H265_UNSPEC55));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,9 @@ class Nalu {
|
|||
H264_EOSeq = 10,
|
||||
H264_FillerData = 12,
|
||||
H264_SPSExtension = 13,
|
||||
H264_PrefixNALUnit = 14,
|
||||
H264_SubsetSPS = 15,
|
||||
H264_DepthParameterSet = 16,
|
||||
H264_Reserved17 = 17,
|
||||
H264_Reserved18 = 18,
|
||||
H264_CodedSliceExtension = 20,
|
||||
|
@ -70,7 +72,12 @@ class Nalu {
|
|||
H265_EOS = 36,
|
||||
H265_EOB = 37,
|
||||
|
||||
H265_PREFIX_SEI = 39,
|
||||
|
||||
H265_RSV_NVCL41 = 41,
|
||||
H265_RSV_NVCL44 = 44,
|
||||
H265_UNSPEC48 = 48,
|
||||
H265_UNSPEC55 = 55,
|
||||
};
|
||||
enum CodecType {
|
||||
kH264,
|
||||
|
@ -96,6 +103,7 @@ class Nalu {
|
|||
|
||||
int type() const { return type_; }
|
||||
bool is_video_slice() const { return is_video_slice_; }
|
||||
bool can_start_access_unit() const { return can_start_access_unit_; }
|
||||
|
||||
private:
|
||||
bool InitializeFromH264(const uint8_t* data, uint64_t size);
|
||||
|
@ -115,6 +123,7 @@ class Nalu {
|
|||
int nuh_temporal_id_;
|
||||
int type_;
|
||||
bool is_video_slice_;
|
||||
bool can_start_access_unit_;
|
||||
|
||||
// Don't use DISALLOW_COPY_AND_ASSIGN since it is just numbers and a pointer
|
||||
// it does not own. This allows Nalus to be stored in a vector.
|
||||
|
|
|
@ -7,9 +7,7 @@
|
|||
#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/avc_decoder_configuration.h"
|
||||
|
@ -21,314 +19,98 @@ namespace edash_packager {
|
|||
namespace media {
|
||||
namespace mp2t {
|
||||
|
||||
namespace {
|
||||
|
||||
// An AUD NALU is at least 4 bytes:
|
||||
// 3 bytes for the start code + 1 byte for the NALU type.
|
||||
const int kMinAUDSize = 4;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
EsParserH264::EsParserH264(uint32_t pid,
|
||||
const NewStreamInfoCB& new_stream_info_cb,
|
||||
const EmitSampleCB& emit_sample_cb)
|
||||
: EsParser(pid),
|
||||
: EsParserH26x(Nalu::kH264, pid, emit_sample_cb),
|
||||
new_stream_info_cb_(new_stream_info_cb),
|
||||
emit_sample_cb_(emit_sample_cb),
|
||||
es_queue_(new media::OffsetByteQueue()),
|
||||
h264_parser_(new H264Parser()),
|
||||
current_access_unit_pos_(0),
|
||||
next_access_unit_pos_(0),
|
||||
stream_converter_(new H264ByteToUnitStreamConverter),
|
||||
decoder_config_check_pending_(false),
|
||||
pending_sample_duration_(0),
|
||||
waiting_for_key_frame_(true) {
|
||||
}
|
||||
h264_parser_(new H264Parser()) {}
|
||||
|
||||
EsParserH264::~EsParserH264() {
|
||||
}
|
||||
|
||||
bool EsParserH264::Parse(const uint8_t* buf,
|
||||
int size,
|
||||
int64_t pts,
|
||||
int64_t dts) {
|
||||
// Note: Parse is invoked each time a PES packet has been reassembled.
|
||||
// Unfortunately, a PES packet does not necessarily map
|
||||
// to an h264 access unit, although the HLS recommendation is to use one PES
|
||||
// for each access unit (but this is just a recommendation and some streams
|
||||
// do not comply with this recommendation).
|
||||
|
||||
// HLS recommendation: "In AVC video, you should have both a DTS and a
|
||||
// PTS in each PES header".
|
||||
// However, some streams do not comply with this recommendation.
|
||||
DVLOG_IF(1, pts == kNoTimestamp) << "Each video PES should have a PTS";
|
||||
if (pts != kNoTimestamp) {
|
||||
TimingDesc timing_desc;
|
||||
timing_desc.pts = pts;
|
||||
timing_desc.dts = (dts != kNoTimestamp) ? dts : pts;
|
||||
|
||||
// Link the end of the byte queue with the incoming timing descriptor.
|
||||
timing_desc_list_.push_back(
|
||||
std::pair<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>();
|
||||
}
|
||||
}
|
||||
EsParserH264::~EsParserH264() {}
|
||||
|
||||
void EsParserH264::Reset() {
|
||||
DVLOG(1) << "EsParserH264::Reset";
|
||||
es_queue_.reset(new media::OffsetByteQueue());
|
||||
h264_parser_.reset(new H264Parser());
|
||||
current_access_unit_pos_ = 0;
|
||||
next_access_unit_pos_ = 0;
|
||||
timing_desc_list_.clear();
|
||||
last_video_decoder_config_ = scoped_refptr<StreamInfo>();
|
||||
decoder_config_check_pending_ = false;
|
||||
pending_sample_ = scoped_refptr<MediaSample>();
|
||||
pending_sample_duration_ = 0;
|
||||
waiting_for_key_frame_ = true;
|
||||
EsParserH26x::Reset();
|
||||
}
|
||||
|
||||
bool EsParserH264::FindAUD(int64_t* stream_pos) {
|
||||
while (true) {
|
||||
const uint8_t* es;
|
||||
int size;
|
||||
es_queue_->PeekAt(*stream_pos, &es, &size);
|
||||
|
||||
// Find a start code and move the stream to the start code parser position.
|
||||
uint64_t start_code_offset;
|
||||
uint8_t start_code_size;
|
||||
bool start_code_found = NaluReader::FindStartCode(
|
||||
es, size, &start_code_offset, &start_code_size);
|
||||
*stream_pos += start_code_offset;
|
||||
|
||||
// No H264 start code found or NALU type not available yet.
|
||||
if (!start_code_found ||
|
||||
start_code_offset + start_code_size >= static_cast<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)
|
||||
bool EsParserH264::ProcessNalu(const Nalu& nalu,
|
||||
bool* is_key_frame,
|
||||
int* pps_id_for_access_unit) {
|
||||
switch (nalu.type()) {
|
||||
case Nalu::H264_AUD: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: AUD";
|
||||
break;
|
||||
|
||||
// The current NALU is not an AUD, skip the start code
|
||||
// and continue parsing the stream.
|
||||
*stream_pos += start_code_size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EsParserH264::ParseInternal() {
|
||||
DCHECK_LE(es_queue_->head(), current_access_unit_pos_);
|
||||
DCHECK_LE(current_access_unit_pos_, next_access_unit_pos_);
|
||||
DCHECK_LE(next_access_unit_pos_, es_queue_->tail());
|
||||
|
||||
// Find the next AUD located at or after |current_access_unit_pos_|. This is
|
||||
// needed since initially |current_access_unit_pos_| might not point to
|
||||
// an AUD.
|
||||
// Discard all the data before the updated |current_access_unit_pos_|
|
||||
// since it won't be used again.
|
||||
bool aud_found = FindAUD(¤t_access_unit_pos_);
|
||||
es_queue_->Trim(current_access_unit_pos_);
|
||||
if (next_access_unit_pos_ < current_access_unit_pos_)
|
||||
next_access_unit_pos_ = current_access_unit_pos_;
|
||||
|
||||
// Resume parsing later if no AUD was found.
|
||||
if (!aud_found)
|
||||
return true;
|
||||
|
||||
// Find the next AUD to make sure we have a complete access unit.
|
||||
if (next_access_unit_pos_ < current_access_unit_pos_ + kMinAUDSize) {
|
||||
next_access_unit_pos_ = current_access_unit_pos_ + kMinAUDSize;
|
||||
DCHECK_LE(next_access_unit_pos_, es_queue_->tail());
|
||||
}
|
||||
if (!FindAUD(&next_access_unit_pos_))
|
||||
return true;
|
||||
|
||||
// At this point, we know we have a full access unit.
|
||||
bool is_key_frame = false;
|
||||
int pps_id_for_access_unit = -1;
|
||||
|
||||
const uint8_t* es;
|
||||
int size;
|
||||
es_queue_->PeekAt(current_access_unit_pos_, &es, &size);
|
||||
int access_unit_size = base::checked_cast<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:
|
||||
}
|
||||
case Nalu::H264_SPS: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: SPS";
|
||||
int sps_id;
|
||||
if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk)
|
||||
return false;
|
||||
}
|
||||
if (is_eos)
|
||||
decoder_config_check_pending_ = true;
|
||||
break;
|
||||
|
||||
switch (nalu.type()) {
|
||||
case Nalu::H264_AUD: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: AUD";
|
||||
break;
|
||||
}
|
||||
case Nalu::H264_SPS: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: SPS";
|
||||
int sps_id;
|
||||
if (h264_parser_->ParseSps(nalu, &sps_id) != H264Parser::kOk)
|
||||
}
|
||||
case Nalu::H264_PPS: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: PPS";
|
||||
int pps_id;
|
||||
if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) {
|
||||
// Allow PPS parsing to fail if waiting for SPS.
|
||||
if (last_video_decoder_config_)
|
||||
return false;
|
||||
} else {
|
||||
decoder_config_check_pending_ = true;
|
||||
break;
|
||||
}
|
||||
case Nalu::H264_PPS: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: PPS";
|
||||
int pps_id;
|
||||
if (h264_parser_->ParsePps(nalu, &pps_id) != H264Parser::kOk) {
|
||||
// Allow PPS parsing to fail if waiting for SPS.
|
||||
if (last_video_decoder_config_)
|
||||
return false;
|
||||
} else {
|
||||
decoder_config_check_pending_ = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Nalu::H264_IDRSlice:
|
||||
case Nalu::H264_NonIDRSlice: {
|
||||
is_key_frame = (nalu.type() == Nalu::H264_IDRSlice);
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: slice IDR=" << is_key_frame;
|
||||
H264SliceHeader shdr;
|
||||
if (h264_parser_->ParseSliceHeader(nalu, &shdr) != H264Parser::kOk) {
|
||||
// Only accept an invalid SPS/PPS at the beginning when the stream
|
||||
// does not necessarily start with an SPS/PPS/IDR.
|
||||
if (last_video_decoder_config_)
|
||||
return false;
|
||||
} else {
|
||||
pps_id_for_access_unit = shdr.pic_parameter_set_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: " << nalu.type();
|
||||
break;
|
||||
}
|
||||
case Nalu::H264_IDRSlice:
|
||||
case Nalu::H264_NonIDRSlice: {
|
||||
*is_key_frame = (nalu.type() == Nalu::H264_IDRSlice);
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: slice IDR=" << is_key_frame;
|
||||
H264SliceHeader shdr;
|
||||
if (h264_parser_->ParseSliceHeader(nalu, &shdr) != H264Parser::kOk) {
|
||||
// Only accept an invalid SPS/PPS at the beginning when the stream
|
||||
// does not necessarily start with an SPS/PPS/IDR.
|
||||
if (last_video_decoder_config_)
|
||||
return false;
|
||||
} else {
|
||||
*pps_id_for_access_unit = shdr.pic_parameter_set_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
DVLOG(LOG_LEVEL_ES) << "Nalu: " << nalu.type();
|
||||
}
|
||||
}
|
||||
|
||||
if (waiting_for_key_frame_) {
|
||||
waiting_for_key_frame_ = !is_key_frame;
|
||||
}
|
||||
if (!waiting_for_key_frame_) {
|
||||
// Emit a frame and move the stream to the next AUD position.
|
||||
RCHECK(EmitFrame(current_access_unit_pos_, access_unit_size,
|
||||
is_key_frame, pps_id_for_access_unit));
|
||||
}
|
||||
current_access_unit_pos_ = next_access_unit_pos_;
|
||||
es_queue_->Trim(current_access_unit_pos_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EsParserH264::EmitFrame(int64_t access_unit_pos,
|
||||
int access_unit_size,
|
||||
bool is_key_frame,
|
||||
int pps_id) {
|
||||
// Get the access unit timing info.
|
||||
TimingDesc current_timing_desc = {kNoTimestamp, kNoTimestamp};
|
||||
while (!timing_desc_list_.empty() &&
|
||||
timing_desc_list_.front().first <= access_unit_pos) {
|
||||
current_timing_desc = timing_desc_list_.front().second;
|
||||
timing_desc_list_.pop_front();
|
||||
}
|
||||
if (current_timing_desc.pts == kNoTimestamp)
|
||||
return false;
|
||||
bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) {
|
||||
// Update the video decoder configuration if needed.
|
||||
if (!decoder_config_check_pending_)
|
||||
return true;
|
||||
|
||||
// Emit a frame.
|
||||
DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_
|
||||
<< " size=" << access_unit_size;
|
||||
int es_size;
|
||||
const uint8_t* es;
|
||||
es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size);
|
||||
CHECK_GE(es_size, access_unit_size);
|
||||
|
||||
// Convert frame to unit stream format.
|
||||
std::vector<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;
|
||||
const H264Pps* pps = h264_parser_->GetPps(pps_id);
|
||||
const H264Sps* sps;
|
||||
if (!pps) {
|
||||
// Only accept an invalid PPS at the beginning when the stream
|
||||
// does not necessarily start with an SPS/PPS/IDR.
|
||||
// In this case, the initial frames are conveyed to the upper layer with
|
||||
// an invalid VideoDecoderConfig and it's up to the upper layer
|
||||
// to process this kind of frame accordingly.
|
||||
return last_video_decoder_config_ == nullptr;
|
||||
} else {
|
||||
sps = h264_parser_->GetSps(pps->seq_parameter_set_id);
|
||||
if (!sps)
|
||||
return false;
|
||||
decoder_config_check_pending_ = false;
|
||||
}
|
||||
|
||||
if (decoder_config_check_pending_) {
|
||||
// Update the video decoder configuration if needed.
|
||||
const H264Pps* pps = h264_parser_->GetPps(pps_id);
|
||||
if (!pps) {
|
||||
// Only accept an invalid PPS at the beginning when the stream
|
||||
// does not necessarily start with an SPS/PPS/IDR.
|
||||
// In this case, the initial frames are conveyed to the upper layer with
|
||||
// an invalid VideoDecoderConfig and it's up to the upper layer
|
||||
// to process this kind of frame accordingly.
|
||||
if (last_video_decoder_config_)
|
||||
return false;
|
||||
} else {
|
||||
const H264Sps* sps = h264_parser_->GetSps(pps->seq_parameter_set_id);
|
||||
if (!sps)
|
||||
return false;
|
||||
RCHECK(UpdateVideoDecoderConfig(sps));
|
||||
decoder_config_check_pending_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the media sample, emitting always the previous sample after
|
||||
// calculating its duration.
|
||||
scoped_refptr<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;
|
||||
if (!stream_converter_->GetDecoderConfigurationRecord(
|
||||
if (!stream_converter()->GetDecoderConfigurationRecord(
|
||||
&decoder_config_record)) {
|
||||
DLOG(ERROR) << "Failure to construct an AVCDecoderConfigurationRecord";
|
||||
return false;
|
||||
|
|
|
@ -7,21 +7,14 @@
|
|||
|
||||
#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.h"
|
||||
#include "packager/media/formats/mp2t/es_parser_h26x.h"
|
||||
|
||||
namespace edash_packager {
|
||||
namespace media {
|
||||
|
||||
class H264ByteToUnitStreamConverter;
|
||||
class H264Parser;
|
||||
class OffsetByteQueue;
|
||||
struct H264Sps;
|
||||
|
||||
namespace mp2t {
|
||||
|
||||
|
@ -30,74 +23,34 @@ namespace mp2t {
|
|||
// Mpeg2 TS spec: "2.14 Carriage of Rec. ITU-T H.264 | ISO/IEC 14496-10 video"
|
||||
// "Each AVC access unit shall contain an access unit delimiter NAL Unit;"
|
||||
//
|
||||
class EsParserH264 : public EsParser {
|
||||
class EsParserH264 : public EsParserH26x {
|
||||
public:
|
||||
EsParserH264(uint32_t pid,
|
||||
const NewStreamInfoCB& new_stream_info_cb,
|
||||
const EmitSampleCB& emit_sample_cb);
|
||||
~EsParserH264() override;
|
||||
|
||||
// EsParser implementation overrides.
|
||||
bool Parse(const uint8_t* buf, int size, int64_t pts, int64_t dts) override;
|
||||
void Flush() override;
|
||||
// EsParserH26x implementation override.
|
||||
void Reset() override;
|
||||
|
||||
private:
|
||||
struct TimingDesc {
|
||||
int64_t dts;
|
||||
int64_t pts;
|
||||
};
|
||||
|
||||
// Find the AUD located at or after |*stream_pos|.
|
||||
// Return true if an AUD is found.
|
||||
// If found, |*stream_pos| corresponds to the position of the AUD start code
|
||||
// in the stream. Otherwise, |*stream_pos| corresponds to the last position
|
||||
// of the start code parser.
|
||||
bool FindAUD(int64_t* stream_pos);
|
||||
|
||||
// Resumes the H264 ES parsing.
|
||||
// Return true if successful.
|
||||
bool ParseInternal();
|
||||
|
||||
// Emit a frame whose position in the ES queue starts at |access_unit_pos|.
|
||||
// Returns true if successful, false if no PTS is available for the frame.
|
||||
bool EmitFrame(int64_t access_unit_pos,
|
||||
int access_unit_size,
|
||||
bool is_key_frame,
|
||||
int pps_id);
|
||||
// Processes a NAL unit found in ParseInternal. The @a pps_id_for_access_unit
|
||||
// value will be passed to UpdateVideoDecoderConfig.
|
||||
bool ProcessNalu(const Nalu& nalu,
|
||||
bool* is_key_frame,
|
||||
int* pps_id_for_access_unit) override;
|
||||
|
||||
// Update the video decoder config based on an H264 SPS.
|
||||
// Return true if successful.
|
||||
bool UpdateVideoDecoderConfig(const H264Sps* sps);
|
||||
bool UpdateVideoDecoderConfig(int sps_id) override;
|
||||
|
||||
// Callbacks to pass the stream configuration and the frames.
|
||||
// Callback to pass the stream configuration.
|
||||
NewStreamInfoCB new_stream_info_cb_;
|
||||
EmitSampleCB emit_sample_cb_;
|
||||
|
||||
// Bytes of the ES stream that have not been emitted yet.
|
||||
scoped_ptr<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_;
|
||||
bool decoder_config_check_pending_;
|
||||
|
||||
// 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_;
|
||||
scoped_ptr<H264Parser> h264_parser_;
|
||||
};
|
||||
|
||||
} // 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_h264.cc',
|
||||
'es_parser_h264.h',
|
||||
'es_parser_h265.cc',
|
||||
'es_parser_h265.h',
|
||||
'es_parser_h26x.cc',
|
||||
'es_parser_h26x.h',
|
||||
'es_parser.h',
|
||||
'mp2t_media_parser.cc',
|
||||
'mp2t_media_parser.h',
|
||||
|
@ -45,6 +49,7 @@
|
|||
],
|
||||
'dependencies': [
|
||||
'../../base/media_base.gyp:media_base',
|
||||
'../../filters/filters.gyp:filters',
|
||||
# TODO(rkuroiwa): AACAudioSpecificConfig is used to create ADTS.
|
||||
# Break this dependency on mp4 by moving it to media/filters.
|
||||
'../../formats/mp4/mp4.gyp:mp4',
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "packager/media/formats/mp2t/es_parser.h"
|
||||
#include "packager/media/formats/mp2t/es_parser_adts.h"
|
||||
#include "packager/media/formats/mp2t/es_parser_h264.h"
|
||||
#include "packager/media/formats/mp2t/es_parser_h265.h"
|
||||
#include "packager/media/formats/mp2t/mp2t_common.h"
|
||||
#include "packager/media/formats/mp2t/ts_packet.h"
|
||||
#include "packager/media/formats/mp2t/ts_section.h"
|
||||
|
@ -28,6 +29,7 @@ enum StreamType {
|
|||
kStreamTypeMpeg1Audio = 0x3,
|
||||
kStreamTypeAAC = 0xf,
|
||||
kStreamTypeAVC = 0x1b,
|
||||
kStreamTypeHEVC = 0x24,
|
||||
};
|
||||
|
||||
class PidState {
|
||||
|
@ -300,6 +302,14 @@ void Mp2tMediaParser::RegisterPes(int pmt_pid,
|
|||
base::Unretained(this)),
|
||||
base::Bind(&Mp2tMediaParser::OnEmitSample,
|
||||
base::Unretained(this))));
|
||||
} else if (stream_type == kStreamTypeHEVC) {
|
||||
es_parser.reset(
|
||||
new EsParserH265(
|
||||
pes_pid,
|
||||
base::Bind(&Mp2tMediaParser::OnNewStreamInfo,
|
||||
base::Unretained(this)),
|
||||
base::Bind(&Mp2tMediaParser::OnEmitSample,
|
||||
base::Unretained(this))));
|
||||
} else if (stream_type == kStreamTypeAAC) {
|
||||
es_parser.reset(
|
||||
new EsParserAdts(
|
||||
|
|
|
@ -73,15 +73,12 @@ class Mp2tMediaParserTest : public testing::Test {
|
|||
|
||||
bool OnNewSample(uint32_t track_id,
|
||||
const scoped_refptr<MediaSample>& sample) {
|
||||
std::string stream_type;
|
||||
StreamMap::const_iterator stream = stream_map_.find(track_id);
|
||||
if (stream != stream_map_.end()) {
|
||||
if (stream->second->stream_type() == kStreamAudio) {
|
||||
++audio_frame_count_;
|
||||
stream_type = "audio";
|
||||
} else if (stream->second->stream_type() == kStreamVideo) {
|
||||
++video_frame_count_;
|
||||
stream_type = "video";
|
||||
if (video_min_dts_ == kNoTimestamp)
|
||||
video_min_dts_ = sample->dts();
|
||||
// Verify timestamps are increasing.
|
||||
|
@ -121,20 +118,36 @@ class Mp2tMediaParserTest : public testing::Test {
|
|||
}
|
||||
};
|
||||
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend17) {
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend17_H264) {
|
||||
// Test small, non-segment-aligned appends.
|
||||
ParseMpeg2TsFile("bear-640x360.ts", 17);
|
||||
EXPECT_EQ(video_frame_count_, 79);
|
||||
EXPECT_EQ(79, video_frame_count_);
|
||||
EXPECT_TRUE(parser_->Flush());
|
||||
EXPECT_EQ(video_frame_count_, 82);
|
||||
EXPECT_EQ(82, video_frame_count_);
|
||||
}
|
||||
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend512) {
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend512_H264) {
|
||||
// Test small, non-segment-aligned appends.
|
||||
ParseMpeg2TsFile("bear-640x360.ts", 512);
|
||||
EXPECT_EQ(video_frame_count_, 79);
|
||||
EXPECT_EQ(79, video_frame_count_);
|
||||
EXPECT_TRUE(parser_->Flush());
|
||||
EXPECT_EQ(video_frame_count_, 82);
|
||||
EXPECT_EQ(82, video_frame_count_);
|
||||
}
|
||||
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend17_H265) {
|
||||
// Test small, non-segment-aligned appends.
|
||||
ParseMpeg2TsFile("bear-640x360-hevc.ts", 17);
|
||||
EXPECT_EQ(79, video_frame_count_);
|
||||
EXPECT_TRUE(parser_->Flush());
|
||||
EXPECT_EQ(82, video_frame_count_);
|
||||
}
|
||||
|
||||
TEST_F(Mp2tMediaParserTest, UnalignedAppend512_H265) {
|
||||
// Test small, non-segment-aligned appends.
|
||||
ParseMpeg2TsFile("bear-640x360-hevc.ts", 512);
|
||||
EXPECT_EQ(79, video_frame_count_);
|
||||
EXPECT_TRUE(parser_->Flush());
|
||||
EXPECT_EQ(82, video_frame_count_);
|
||||
}
|
||||
|
||||
TEST_F(Mp2tMediaParserTest, TimestampWrapAround) {
|
||||
|
@ -143,7 +156,7 @@ TEST_F(Mp2tMediaParserTest, TimestampWrapAround) {
|
|||
// wrap around in the Mpeg2 TS stream.
|
||||
ParseMpeg2TsFile("bear-640x360_ptswraparound.ts", 512);
|
||||
EXPECT_TRUE(parser_->Flush());
|
||||
EXPECT_EQ(video_frame_count_, 82);
|
||||
EXPECT_EQ(82, video_frame_count_);
|
||||
EXPECT_LT(video_min_dts_, static_cast<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_ptswraparound.ts - Same as bear-640x360.ts, with a timestamp wrap-around in the middle, created with the below command:
|
||||
ffmpeg -itsoffset 95442 -i bear-640x360.ts -c:v copy -c:a copy -muxdelay 0 bear-640x360_ptswraparound.ts
|
||||
bear-640x360-hevc.ts - HEVC + AAC encode, multiplexed into an MPEG2-TS container.
|
||||
|
||||
// ISO-BMFF streams.
|
||||
bear-1280x720.mp4 - AVC + AAC encode, mulitplexed into an ISOBMFF container.
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue