| Index: media/mpeg2/mpeg2ts_pes.cc
|
| diff --git a/media/mpeg2/mpeg2ts_pes.cc b/media/mpeg2/mpeg2ts_pes.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..748b83d64085151e5ff73cf7c3db96d02ebfc603
|
| --- /dev/null
|
| +++ b/media/mpeg2/mpeg2ts_pes.cc
|
| @@ -0,0 +1,370 @@
|
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "media/mpeg2/mpeg2ts_pes.h"
|
| +
|
| +#include "base/logging.h"
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "media/base/bit_reader.h"
|
| +#include "media/mpeg2/es_parser.h"
|
| +#include "media/mpeg2/mpeg2ts_common.h"
|
| +#include "media/mpeg2/mpeg2ts_def.h"
|
| +
|
| +namespace {
|
| +
|
| +int64 UnrollTimestamp(int64 previous_unrolled_time,
|
| + int64 time,
|
| + int nbits) {
|
| + // |timestamp| has a precision of |nbits|
|
| + // so make sure the highest bits are set to 0.
|
| + DCHECK_EQ((time >> nbits), 0);
|
| +
|
| + // Consider 3 possibilities to estimate the missing high bits of |time|.
|
| + int64 previous_unrolled_time_high =
|
| + (previous_unrolled_time >> nbits);
|
| + int64 time0 = ((previous_unrolled_time_high - 1) << nbits) | time;
|
| + int64 time1 = ((previous_unrolled_time_high + 0) << nbits) | time;
|
| + int64 time2 = ((previous_unrolled_time_high + 1) << nbits) | time;
|
| +
|
| + // Select the min absolute difference with the current time
|
| + // so as to ensure time continuity.
|
| + int64 diff0 = time0 - previous_unrolled_time;
|
| + int64 diff1 = time1 - previous_unrolled_time;
|
| + int64 diff2 = time2 - previous_unrolled_time;
|
| + if (diff0 < 0) {
|
| + diff0 = -diff0;
|
| + }
|
| + if (diff1 < 0) {
|
| + diff1 = -diff1;
|
| + }
|
| + if (diff2 < 0) {
|
| + diff2 = -diff2;
|
| + }
|
| +
|
| + int64 unrolled_time;
|
| + int64 min_diff;
|
| + if (diff1 < diff0) {
|
| + unrolled_time = time1;
|
| + min_diff = diff1;
|
| + } else {
|
| + unrolled_time = time0;
|
| + min_diff = diff0;
|
| + }
|
| + if (diff2 < min_diff) {
|
| + unrolled_time = time2;
|
| + }
|
| + LOG_IF(INFO, unrolled_time != time1)
|
| + << "Unrolling time:"
|
| + << " previous_unrolled_time=" << previous_unrolled_time
|
| + << " time=" << time
|
| + << " unrolled_time=" << unrolled_time;
|
| +
|
| + return unrolled_time;
|
| +}
|
| +
|
| +bool IsTimestampSectionValid(int64 timestamp_section) {
|
| + // |pts_section| has 40 bits:
|
| + // - starting with either '0010' or '0011' or '0001'
|
| + // - and ending with a marker bit.
|
| + // See ITU H.222 standard - PES section.
|
| +
|
| + // Verify that all the marker bits are one.
|
| + bool is_valid =
|
| + ((timestamp_section & 0x1) != 0) &&
|
| + ((timestamp_section & 0x10000) != 0) &&
|
| + ((timestamp_section & 0x100000000) != 0);
|
| + return is_valid;
|
| +}
|
| +
|
| +int64 ConvertTimestampSectionToTimestamp(int64 timestamp_section) {
|
| + int64 timestamp =
|
| + (((timestamp_section >> 33) & 0x7) << 30) |
|
| + (((timestamp_section >> 17) & 0x7fff) << 15) |
|
| + (((timestamp_section >> 1) & 0x7fff) << 0);
|
| + return timestamp;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +namespace media {
|
| +namespace mpeg2ts {
|
| +
|
| +Mpeg2TsPesParser::Mpeg2TsPesParser(EsParser* es_parser)
|
| + : es_parser_(es_parser),
|
| + wait_for_unit_start_(true),
|
| + previous_pts_valid_(false),
|
| + previous_pts_(0),
|
| + previous_dts_valid_(false),
|
| + previous_dts_(0) {
|
| +}
|
| +
|
| +Mpeg2TsPesParser::~Mpeg2TsPesParser() {
|
| +}
|
| +
|
| +bool Mpeg2TsPesParser::Parse(bool payload_unit_start_indicator,
|
| + const uint8* buf, int size) {
|
| + if (wait_for_unit_start_ && !payload_unit_start_indicator) {
|
| + // Ignore partial PES.
|
| + return true;
|
| + }
|
| + wait_for_unit_start_ = false;
|
| +
|
| + if (payload_unit_start_indicator) {
|
| + // If we have a pending packet with an undefined size
|
| + // (size == 0), try to parse and emit the previous packet.
|
| + if (raw_pes_.size() > 0) {
|
| + // TODO(damienv): should check the result of InternalParse for a possible
|
| + // error.
|
| + InternalParse();
|
| + raw_pes_.resize(0);
|
| + }
|
| + }
|
| +
|
| + // Add the data to the parser state.
|
| + if (size > 0) {
|
| + int old_size = raw_pes_.size();
|
| + raw_pes_.resize(old_size + size);
|
| + memcpy(&raw_pes_[old_size], buf, size);
|
| + }
|
| +
|
| + // Check whether we have enough data to start parsing.
|
| + if (raw_pes_.size() < 6)
|
| + return true;
|
| + int pes_packet_length =
|
| + (static_cast<int>(raw_pes_[4]) << 8) |
|
| + (static_cast<int>(raw_pes_[5]));
|
| + VLOG(LOG_LEVEL_PES) << "pes_packet_length=" << pes_packet_length;
|
| + if (pes_packet_length == 0) {
|
| + // Video packets are emitted when a new unit is coming
|
| + // since there is no way to know the size of the packet before.
|
| + return true;
|
| + }
|
| + if (static_cast<int>(raw_pes_.size()) < pes_packet_length + 6) {
|
| + // Don't throw an error when there is not enough data,
|
| + // just wait for more data to come.
|
| + return true;
|
| + }
|
| +
|
| + // Parse the packet.
|
| + bool parse_result = InternalParse();
|
| + raw_pes_.resize(0);
|
| + return parse_result;
|
| +}
|
| +
|
| +void Mpeg2TsPesParser::Flush() {
|
| + // Force emitting the previous buffer by faking a
|
| + // payload_unit_start_indicator.
|
| + // For video, the pes packet length is set to 0,
|
| + // thus a video packet can only be emitted when the next video
|
| + // packet is coming.
|
| + Parse(true, NULL, 0);
|
| + raw_pes_.resize(0);
|
| +
|
| + // Flush the underlying ES parser.
|
| + es_parser_->Flush();
|
| +}
|
| +
|
| +bool Mpeg2TsPesParser::InternalParse() {
|
| + BitReader bit_reader(&raw_pes_[0], raw_pes_.size());
|
| + int remaining_size = raw_pes_.size();
|
| +
|
| + // Read:
|
| + // - packet_start_code_prefix
|
| + // - stream_id
|
| + // - pes_packet_length
|
| + if (remaining_size < 6) {
|
| + return false;
|
| + }
|
| + int packet_start_code_prefix;
|
| + DCHECK(bit_reader.ReadBits(24, &packet_start_code_prefix));
|
| + if (packet_start_code_prefix != 0x000001) {
|
| + LOG(WARNING) << "Invalid PES start code: "
|
| + << std::hex << "0x" << packet_start_code_prefix << std::dec;
|
| + return false;
|
| + }
|
| + int stream_id;
|
| + DCHECK(bit_reader.ReadBits(8, &stream_id));
|
| + VLOG(LOG_LEVEL_PES) << "stream_id=" << std::hex << stream_id << std::dec;
|
| + int pes_packet_length;
|
| + DCHECK(bit_reader.ReadBits(16, &pes_packet_length));
|
| + bool pes_packet_length_defined = (pes_packet_length != 0);
|
| + remaining_size -= 6;
|
| +
|
| + if (stream_id != kProgramStreamMap &&
|
| + stream_id != kPaddingStream &&
|
| + stream_id != kPrivateStream2 &&
|
| + stream_id != kEcmStream &&
|
| + stream_id != kEmmStream &&
|
| + stream_id != kProgramStreamDirectory &&
|
| + stream_id != kDsmccStream &&
|
| + stream_id != kItu222TypeEStream) {
|
| + if (remaining_size < 3) {
|
| + return false;
|
| + }
|
| + int dummy_2;
|
| + DCHECK(bit_reader.ReadBits(2, &dummy_2));
|
| + if (dummy_2 != 0x2) {
|
| + LOG(WARNING) << "Unexpected bits";
|
| + return false;
|
| + }
|
| + int PES_scrambling_control;
|
| + DCHECK(bit_reader.ReadBits(2, &PES_scrambling_control));
|
| + bool PES_priority;
|
| + DCHECK(bit_reader.ReadBits(1, &PES_priority));
|
| + bool data_alignment_indicator;
|
| + DCHECK(bit_reader.ReadBits(1, &data_alignment_indicator));
|
| + bool copyright;
|
| + DCHECK(bit_reader.ReadBits(1, ©right));
|
| + bool original_or_copy;
|
| + DCHECK(bit_reader.ReadBits(1, &original_or_copy));
|
| + int pts_dts_flags;
|
| + DCHECK(bit_reader.ReadBits(2, &pts_dts_flags));
|
| + bool escr_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &escr_flag));
|
| + bool es_rate_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &es_rate_flag));
|
| + bool dsm_trick_mode_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &dsm_trick_mode_flag));
|
| + bool additional_copy_info_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &additional_copy_info_flag));
|
| + bool pes_crc_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &pes_crc_flag));
|
| + bool pes_extension_flag;
|
| + DCHECK(bit_reader.ReadBits(1, &pes_extension_flag));
|
| + int pes_header_data_length;
|
| + DCHECK(bit_reader.ReadBits(8, &pes_header_data_length));
|
| + remaining_size -= 3;
|
| + pes_packet_length -= 3;
|
| +
|
| + bool is_pts_valid = false;
|
| + bool is_dts_valid = false;
|
| + int64 pts_section = 0;
|
| + int64 dts_section = 0;
|
| + if (pts_dts_flags == 0x2) {
|
| + if (remaining_size < 5) {
|
| + return false;
|
| + }
|
| + DCHECK(bit_reader.ReadBits(40, &pts_section));
|
| + if (((pts_section >> 36) & 0xf) != 0x2 ||
|
| + !IsTimestampSectionValid(pts_section)) {
|
| + LOG(WARNING) << "Invalid PTS section: "
|
| + << std::hex << "0x" << pts_section << std::dec;
|
| + return false;
|
| + }
|
| + is_pts_valid = true;
|
| + remaining_size -= 5;
|
| + pes_packet_length -= 5;
|
| + pes_header_data_length -= 5;
|
| + }
|
| +
|
| + if (pts_dts_flags == 0x3) {
|
| + if (remaining_size < 10) {
|
| + return false;
|
| + }
|
| + DCHECK(bit_reader.ReadBits(40, &pts_section));
|
| + if (((pts_section >> 36) & 0xf) != 0x3 ||
|
| + !IsTimestampSectionValid(pts_section)) {
|
| + LOG(WARNING) << "Invalid PTS section: "
|
| + << std::hex << "0x" << pts_section << std::dec;
|
| + return false;
|
| + }
|
| + DCHECK(bit_reader.ReadBits(40, &dts_section));
|
| + if (((dts_section >> 36) & 0xf) != 0x1 ||
|
| + !IsTimestampSectionValid(dts_section)) {
|
| + LOG(WARNING) << "Invalid DTS section: "
|
| + << std::hex << "0x" << dts_section << std::dec;
|
| + return false;
|
| + }
|
| + is_pts_valid = true;
|
| + is_dts_valid = true;
|
| + remaining_size -= 10;
|
| + pes_packet_length -= 10;
|
| + pes_header_data_length -= 10;
|
| + }
|
| +
|
| + int64 pts = 0;
|
| + int64 dts = 0;
|
| + base::TimeDelta media_pts;
|
| + base::TimeDelta media_dts;
|
| + if (is_pts_valid) {
|
| + pts = ConvertTimestampSectionToTimestamp(pts_section);
|
| + if (previous_pts_valid_) {
|
| + pts = UnrollTimestamp(previous_pts_, pts, 33);
|
| + }
|
| + previous_pts_ = pts;
|
| + previous_pts_valid_ = true;
|
| + media_pts = base::TimeDelta::FromMicroseconds((1000 * pts) / 90);
|
| + }
|
| +
|
| + if (is_dts_valid) {
|
| + dts = ConvertTimestampSectionToTimestamp(dts_section);
|
| + if (previous_dts_valid_) {
|
| + dts = UnrollTimestamp(previous_dts_, dts, 33);
|
| + }
|
| + previous_dts_ = dts;
|
| + previous_dts_valid_ = true;
|
| + media_dts = base::TimeDelta::FromMicroseconds((1000 * dts) / 90);
|
| + }
|
| +
|
| + // TODO(damienv): check whether we need some info
|
| + // from the rest of the PES packet header.
|
| + if (pes_header_data_length < 0) {
|
| + LOG(WARNING) << "Invalid PES header length";
|
| + return false;
|
| + }
|
| + if (pes_header_data_length > 0) {
|
| + if (remaining_size < pes_header_data_length) {
|
| + return false;
|
| + }
|
| + for (int k = 0; k < pes_header_data_length; k++) {
|
| + int dummy;
|
| + DCHECK(bit_reader.ReadBits(8, &dummy));
|
| + }
|
| + remaining_size -= pes_header_data_length;
|
| + pes_packet_length -= pes_header_data_length;
|
| + }
|
| +
|
| + // Read the PES packet.
|
| + if (pes_packet_length_defined && pes_packet_length < 0) {
|
| + LOG(WARNING) << "Invalid ES length: " << pes_packet_length;
|
| + return false;
|
| + }
|
| + int es_size = pes_packet_length;
|
| + if (!pes_packet_length_defined) {
|
| + es_size = remaining_size;
|
| + }
|
| + VLOG(LOG_LEVEL_PES)
|
| + << "Emit a reassembled PES:"
|
| + << " size=" << es_size
|
| + << " pts=" << pts
|
| + << " dts=" << dts
|
| + << " data_alignment_indicator=" << data_alignment_indicator;
|
| +
|
| + int dump_count = 16;
|
| + if (es_size < 16)
|
| + dump_count = es_size;
|
| + int es_offset = raw_pes_.size() - remaining_size;
|
| + VLOG(LOG_LEVEL_PES)
|
| + << "=> " << base::HexEncode(&raw_pes_[es_offset], dump_count);
|
| + if (es_parser_) {
|
| + es_parser_->Parse(&raw_pes_[es_offset], es_size,
|
| + is_pts_valid, media_pts,
|
| + is_dts_valid, media_dts);
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + if (stream_id == kPaddingStream) {
|
| + return true;
|
| + }
|
| +
|
| + // Handle the remaining stream ids.
|
| + VLOG(LOG_LEVEL_PES)
|
| + << "PES packet:"
|
| + << " size=" << pes_packet_length;
|
| + return true;
|
| +}
|
| +
|
| +} // namespace mpeg2ts
|
| +} // namespace media
|
|
|