Check-in of unmodified MPEG-2 and H.264 parsers.

Source of media/formats/mp2t:
    http://src.chromium.org/chrome/trunk/src/media/formats/mp2t@260741

Source of media/filters:
    http://src.chromium.org/chrome/trunk/src/media/filters@260719

Change-Id: Ib4c72553f0213cb6dd25fa3dcc0367d96cdd094a
This commit is contained in:
Thomas Inskip 2014-03-31 18:34:59 -07:00
parent 5f99651223
commit 0ad332896e
27 changed files with 5275 additions and 0 deletions

View File

@ -0,0 +1,113 @@
// 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 "base/logging.h"
#include "media/filters/h264_bit_reader.h"
namespace media {
H264BitReader::H264BitReader()
: data_(NULL),
bytes_left_(0),
curr_byte_(0),
num_remaining_bits_in_curr_byte_(0),
prev_two_bytes_(0),
emulation_prevention_bytes_(0) {}
H264BitReader::~H264BitReader() {}
bool H264BitReader::Initialize(const uint8* data, off_t size) {
DCHECK(data);
if (size < 1)
return false;
data_ = data;
bytes_left_ = size;
num_remaining_bits_in_curr_byte_ = 0;
// Initially set to 0xffff to accept all initial two-byte sequences.
prev_two_bytes_ = 0xffff;
emulation_prevention_bytes_ = 0;
return true;
}
bool H264BitReader::UpdateCurrByte() {
if (bytes_left_ < 1)
return false;
// Emulation prevention three-byte detection.
// If a sequence of 0x000003 is found, skip (ignore) the last byte (0x03).
if (*data_ == 0x03 && (prev_two_bytes_ & 0xffff) == 0) {
// Detected 0x000003, skip last byte.
++data_;
--bytes_left_;
++emulation_prevention_bytes_;
// Need another full three bytes before we can detect the sequence again.
prev_two_bytes_ = 0xffff;
if (bytes_left_ < 1)
return false;
}
// Load a new byte and advance pointers.
curr_byte_ = *data_++ & 0xff;
--bytes_left_;
num_remaining_bits_in_curr_byte_ = 8;
prev_two_bytes_ = (prev_two_bytes_ << 8) | curr_byte_;
return true;
}
// Read |num_bits| (1 to 31 inclusive) from the stream and return them
// in |out|, with first bit in the stream as MSB in |out| at position
// (|num_bits| - 1).
bool H264BitReader::ReadBits(int num_bits, int* out) {
int bits_left = num_bits;
*out = 0;
DCHECK(num_bits <= 31);
while (num_remaining_bits_in_curr_byte_ < bits_left) {
// Take all that's left in current byte, shift to make space for the rest.
*out |= (curr_byte_ << (bits_left - num_remaining_bits_in_curr_byte_));
bits_left -= num_remaining_bits_in_curr_byte_;
if (!UpdateCurrByte())
return false;
}
*out |= (curr_byte_ >> (num_remaining_bits_in_curr_byte_ - bits_left));
*out &= ((1 << num_bits) - 1);
num_remaining_bits_in_curr_byte_ -= bits_left;
return true;
}
off_t H264BitReader::NumBitsLeft() {
return (num_remaining_bits_in_curr_byte_ + bytes_left_ * 8);
}
bool H264BitReader::HasMoreRBSPData() {
// Make sure we have more bits, if we are at 0 bits in current byte
// and updating current byte fails, we don't have more data anyway.
if (num_remaining_bits_in_curr_byte_ == 0 && !UpdateCurrByte())
return false;
// On last byte?
if (bytes_left_)
return true;
// Last byte, look for stop bit;
// We have more RBSP data if the last non-zero bit we find is not the
// first available bit.
return (curr_byte_ &
((1 << (num_remaining_bits_in_curr_byte_ - 1)) - 1)) != 0;
}
size_t H264BitReader::NumEmulationPreventionBytesRead() {
return emulation_prevention_bytes_;
}
} // namespace media

View File

@ -0,0 +1,79 @@
// 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.
//
// This file contains an implementation of an H264 Annex-B video stream parser.
#ifndef MEDIA_FILTERS_H264_BIT_READER_H_
#define MEDIA_FILTERS_H264_BIT_READER_H_
#include <sys/types.h>
#include "base/basictypes.h"
#include "media/base/media_export.h"
namespace media {
// A class to provide bit-granularity reading of H.264 streams.
// This is not a generic bit reader class, as it takes into account
// H.264 stream-specific constraints, such as skipping emulation-prevention
// bytes and stop bits. See spec for more details.
class MEDIA_EXPORT H264BitReader {
public:
H264BitReader();
~H264BitReader();
// Initialize the reader to start reading at |data|, |size| being size
// of |data| in bytes.
// Return false on insufficient size of stream..
// TODO(posciak,fischman): consider replacing Initialize() with
// heap-allocating and creating bit readers on demand instead.
bool Initialize(const uint8* data, off_t size);
// Read |num_bits| next bits from stream and return in |*out|, first bit
// from the stream starting at |num_bits| position in |*out|.
// |num_bits| may be 1-32, inclusive.
// Return false if the given number of bits cannot be read (not enough
// bits in the stream), true otherwise.
bool ReadBits(int num_bits, int* out);
// Return the number of bits left in the stream.
off_t NumBitsLeft();
// See the definition of more_rbsp_data() in spec.
bool HasMoreRBSPData();
// Return the number of emulation prevention bytes already read.
size_t NumEmulationPreventionBytesRead();
private:
// Advance to the next byte, loading it into curr_byte_.
// Return false on end of stream.
bool UpdateCurrByte();
// Pointer to the next unread (not in curr_byte_) byte in the stream.
const uint8* data_;
// Bytes left in the stream (without the curr_byte_).
off_t bytes_left_;
// Contents of the current byte; first unread bit starting at position
// 8 - num_remaining_bits_in_curr_byte_ from MSB.
int curr_byte_;
// Number of bits remaining in curr_byte_
int num_remaining_bits_in_curr_byte_;
// Used in emulation prevention three byte detection (see spec).
// Initially set to 0xffff to accept all initial two-byte sequences.
int prev_two_bytes_;
// Number of emulation preventation bytes (0x000003) we met.
size_t emulation_prevention_bytes_;
DISALLOW_COPY_AND_ASSIGN(H264BitReader);
};
} // namespace media
#endif // MEDIA_FILTERS_H264_BIT_READER_H_

View File

@ -0,0 +1,73 @@
// 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 "media/filters/h264_bit_reader.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
TEST(H264BitReaderTest, ReadStreamWithoutEscapeAndTrailingZeroBytes) {
H264BitReader reader;
const unsigned char rbsp[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xa0};
int dummy = 0;
EXPECT_TRUE(reader.Initialize(rbsp, sizeof(rbsp)));
EXPECT_TRUE(reader.ReadBits(1, &dummy));
EXPECT_EQ(dummy, 0x00);
EXPECT_EQ(reader.NumBitsLeft(), 47);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(8, &dummy));
EXPECT_EQ(dummy, 0x02);
EXPECT_EQ(reader.NumBitsLeft(), 39);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(31, &dummy));
EXPECT_EQ(dummy, 0x23456789);
EXPECT_EQ(reader.NumBitsLeft(), 8);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(1, &dummy));
EXPECT_EQ(dummy, 1);
EXPECT_EQ(reader.NumBitsLeft(), 7);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(1, &dummy));
EXPECT_EQ(dummy, 0);
EXPECT_EQ(reader.NumBitsLeft(), 6);
EXPECT_FALSE(reader.HasMoreRBSPData());
}
TEST(H264BitReaderTest, SingleByteStream) {
H264BitReader reader;
const unsigned char rbsp[] = {0x18};
int dummy = 0;
EXPECT_TRUE(reader.Initialize(rbsp, sizeof(rbsp)));
EXPECT_EQ(reader.NumBitsLeft(), 8);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(4, &dummy));
EXPECT_EQ(dummy, 0x01);
EXPECT_EQ(reader.NumBitsLeft(), 4);
EXPECT_FALSE(reader.HasMoreRBSPData());
}
TEST(H264BitReaderTest, StopBitOccupyFullByte) {
H264BitReader reader;
const unsigned char rbsp[] = {0xab, 0x80};
int dummy = 0;
EXPECT_TRUE(reader.Initialize(rbsp, sizeof(rbsp)));
EXPECT_EQ(reader.NumBitsLeft(), 16);
EXPECT_TRUE(reader.HasMoreRBSPData());
EXPECT_TRUE(reader.ReadBits(8, &dummy));
EXPECT_EQ(dummy, 0xab);
EXPECT_EQ(reader.NumBitsLeft(), 8);
EXPECT_FALSE(reader.HasMoreRBSPData());
}
} // namespace media

1264
media/filters/h264_parser.cc Normal file

File diff suppressed because it is too large Load Diff

399
media/filters/h264_parser.h Normal file
View File

@ -0,0 +1,399 @@
// 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.
//
// This file contains an implementation of an H264 Annex-B video stream parser.
#ifndef MEDIA_FILTERS_H264_PARSER_H_
#define MEDIA_FILTERS_H264_PARSER_H_
#include <sys/types.h>
#include <map>
#include "base/basictypes.h"
#include "media/base/media_export.h"
#include "media/filters/h264_bit_reader.h"
namespace media {
// For explanations of each struct and its members, see H.264 specification
// at http://www.itu.int/rec/T-REC-H.264.
struct MEDIA_EXPORT H264NALU {
H264NALU();
enum Type {
kUnspecified = 0,
kNonIDRSlice = 1,
kIDRSlice = 5,
kSEIMessage = 6,
kSPS = 7,
kPPS = 8,
kAUD = 9,
kEOSeq = 10,
kEOStream = 11,
kCodedSliceExtension = 20,
};
// After (without) start code; we don't own the underlying memory
// and a shallow copy should be made when copying this struct.
const uint8* data;
off_t size; // From after start code to start code of next NALU (or EOS).
int nal_ref_idc;
int nal_unit_type;
};
enum {
kH264ScalingList4x4Length = 16,
kH264ScalingList8x8Length = 64,
};
struct MEDIA_EXPORT H264SPS {
H264SPS();
int profile_idc;
bool constraint_set0_flag;
bool constraint_set1_flag;
bool constraint_set2_flag;
bool constraint_set3_flag;
bool constraint_set4_flag;
bool constraint_set5_flag;
int level_idc;
int seq_parameter_set_id;
int chroma_format_idc;
bool separate_colour_plane_flag;
int bit_depth_luma_minus8;
int bit_depth_chroma_minus8;
bool qpprime_y_zero_transform_bypass_flag;
bool seq_scaling_matrix_present_flag;
int scaling_list4x4[6][kH264ScalingList4x4Length];
int scaling_list8x8[6][kH264ScalingList8x8Length];
int log2_max_frame_num_minus4;
int pic_order_cnt_type;
int log2_max_pic_order_cnt_lsb_minus4;
bool delta_pic_order_always_zero_flag;
int offset_for_non_ref_pic;
int offset_for_top_to_bottom_field;
int num_ref_frames_in_pic_order_cnt_cycle;
int expected_delta_per_pic_order_cnt_cycle; // calculated
int offset_for_ref_frame[255];
int max_num_ref_frames;
bool gaps_in_frame_num_value_allowed_flag;
int pic_width_in_mbs_minus1;
int pic_height_in_map_units_minus1;
bool frame_mbs_only_flag;
bool mb_adaptive_frame_field_flag;
bool direct_8x8_inference_flag;
bool frame_cropping_flag;
int frame_crop_left_offset;
int frame_crop_right_offset;
int frame_crop_top_offset;
int frame_crop_bottom_offset;
bool vui_parameters_present_flag;
int sar_width; // Set to 0 when not specified.
int sar_height; // Set to 0 when not specified.
bool bitstream_restriction_flag;
int max_num_reorder_frames;
int max_dec_frame_buffering;
int chroma_array_type;
};
struct MEDIA_EXPORT H264PPS {
H264PPS();
int pic_parameter_set_id;
int seq_parameter_set_id;
bool entropy_coding_mode_flag;
bool bottom_field_pic_order_in_frame_present_flag;
int num_slice_groups_minus1;
// TODO(posciak): Slice groups not implemented, could be added at some point.
int num_ref_idx_l0_default_active_minus1;
int num_ref_idx_l1_default_active_minus1;
bool weighted_pred_flag;
int weighted_bipred_idc;
int pic_init_qp_minus26;
int pic_init_qs_minus26;
int chroma_qp_index_offset;
bool deblocking_filter_control_present_flag;
bool constrained_intra_pred_flag;
bool redundant_pic_cnt_present_flag;
bool transform_8x8_mode_flag;
bool pic_scaling_matrix_present_flag;
int scaling_list4x4[6][kH264ScalingList4x4Length];
int scaling_list8x8[6][kH264ScalingList8x8Length];
int second_chroma_qp_index_offset;
};
struct MEDIA_EXPORT H264ModificationOfPicNum {
int modification_of_pic_nums_idc;
union {
int abs_diff_pic_num_minus1;
int long_term_pic_num;
};
};
struct MEDIA_EXPORT H264WeightingFactors {
bool luma_weight_flag;
bool chroma_weight_flag;
int luma_weight[32];
int luma_offset[32];
int chroma_weight[32][2];
int chroma_offset[32][2];
};
struct MEDIA_EXPORT H264DecRefPicMarking {
int memory_mgmnt_control_operation;
int difference_of_pic_nums_minus1;
int long_term_pic_num;
int long_term_frame_idx;
int max_long_term_frame_idx_plus1;
};
struct MEDIA_EXPORT H264SliceHeader {
H264SliceHeader();
enum {
kRefListSize = 32,
kRefListModSize = kRefListSize
};
enum Type {
kPSlice = 0,
kBSlice = 1,
kISlice = 2,
kSPSlice = 3,
kSISlice = 4,
};
bool IsPSlice() const;
bool IsBSlice() const;
bool IsISlice() const;
bool IsSPSlice() const;
bool IsSISlice() const;
bool idr_pic_flag; // from NAL header
int nal_ref_idc; // from NAL header
const uint8* nalu_data; // from NAL header
off_t nalu_size; // from NAL header
off_t header_bit_size; // calculated
int first_mb_in_slice;
int slice_type;
int pic_parameter_set_id;
int colour_plane_id; // TODO(posciak): use this! http://crbug.com/139878
int frame_num;
bool field_pic_flag;
bool bottom_field_flag;
int idr_pic_id;
int pic_order_cnt_lsb;
int delta_pic_order_cnt_bottom;
int delta_pic_order_cnt[2];
int redundant_pic_cnt;
bool direct_spatial_mv_pred_flag;
bool num_ref_idx_active_override_flag;
int num_ref_idx_l0_active_minus1;
int num_ref_idx_l1_active_minus1;
bool ref_pic_list_modification_flag_l0;
bool ref_pic_list_modification_flag_l1;
H264ModificationOfPicNum ref_list_l0_modifications[kRefListModSize];
H264ModificationOfPicNum ref_list_l1_modifications[kRefListModSize];
int luma_log2_weight_denom;
int chroma_log2_weight_denom;
bool luma_weight_l0_flag;
bool chroma_weight_l0_flag;
H264WeightingFactors pred_weight_table_l0;
bool luma_weight_l1_flag;
bool chroma_weight_l1_flag;
H264WeightingFactors pred_weight_table_l1;
bool no_output_of_prior_pics_flag;
bool long_term_reference_flag;
bool adaptive_ref_pic_marking_mode_flag;
H264DecRefPicMarking ref_pic_marking[kRefListSize];
int cabac_init_idc;
int slice_qp_delta;
bool sp_for_switch_flag;
int slice_qs_delta;
int disable_deblocking_filter_idc;
int slice_alpha_c0_offset_div2;
int slice_beta_offset_div2;
};
struct H264SEIRecoveryPoint {
int recovery_frame_cnt;
bool exact_match_flag;
bool broken_link_flag;
int changing_slice_group_idc;
};
struct MEDIA_EXPORT H264SEIMessage {
H264SEIMessage();
enum Type {
kSEIRecoveryPoint = 6,
};
int type;
int payload_size;
union {
// Placeholder; in future more supported types will contribute to more
// union members here.
H264SEIRecoveryPoint recovery_point;
};
};
// Class to parse an Annex-B H.264 stream,
// as specified in chapters 7 and Annex B of the H.264 spec.
class MEDIA_EXPORT H264Parser {
public:
enum Result {
kOk,
kInvalidStream, // error in stream
kUnsupportedStream, // stream not supported by the parser
kEOStream, // end of stream
};
// Find offset from start of data to next NALU start code
// and size of found start code (3 or 4 bytes).
// If no start code is found, offset is pointing to the first unprocessed byte
// (i.e. the first byte that was not considered as a possible start of a start
// code) and |*start_code_size| is set to 0.
// Preconditions:
// - |data_size| >= 0
// Postconditions:
// - |*offset| is between 0 and |data_size| included.
// It is strictly less than |data_size| if |data_size| > 0.
// - |*start_code_size| is either 0, 3 or 4.
static bool FindStartCode(const uint8* data, off_t data_size,
off_t* offset, off_t* start_code_size);
H264Parser();
~H264Parser();
void Reset();
// Set current stream pointer to |stream| of |stream_size| in bytes,
// |stream| owned by caller.
void SetStream(const uint8* stream, off_t stream_size);
// Read the stream to find the next NALU, identify it and return
// that information in |*nalu|. This advances the stream to the beginning
// of this NALU, but not past it, so subsequent calls to NALU-specific
// parsing functions (ParseSPS, etc.) will parse this NALU.
// If the caller wishes to skip the current NALU, it can call this function
// again, instead of any NALU-type specific parse functions below.
Result AdvanceToNextNALU(H264NALU* nalu);
// NALU-specific parsing functions.
// These should be called after AdvanceToNextNALU().
// SPSes and PPSes are owned by the parser class and the memory for their
// structures is managed here, not by the caller, as they are reused
// across NALUs.
//
// Parse an SPS/PPS NALU and save their data in the parser, returning id
// of the parsed structure in |*pps_id|/|*sps_id|.
// To get a pointer to a given SPS/PPS structure, use GetSPS()/GetPPS(),
// passing the returned |*sps_id|/|*pps_id| as parameter.
// TODO(posciak,fischman): consider replacing returning Result from Parse*()
// methods with a scoped_ptr and adding an AtEOS() function to check for EOS
// if Parse*() return NULL.
Result ParseSPS(int* sps_id);
Result ParsePPS(int* pps_id);
// Return a pointer to SPS/PPS with given |sps_id|/|pps_id| or NULL if not
// present.
const H264SPS* GetSPS(int sps_id);
const H264PPS* GetPPS(int pps_id);
// Slice headers and SEI messages are not used across NALUs by the parser
// and can be discarded after current NALU, so the parser does not store
// them, nor does it manage their memory.
// The caller has to provide and manage it instead.
// Parse a slice header, returning it in |*shdr|. |*nalu| must be set to
// the NALU returned from AdvanceToNextNALU() and corresponding to |*shdr|.
Result ParseSliceHeader(const H264NALU& nalu, H264SliceHeader* shdr);
// Parse a SEI message, returning it in |*sei_msg|, provided and managed
// by the caller.
Result ParseSEI(H264SEIMessage* sei_msg);
private:
// Move the stream pointer to the beginning of the next NALU,
// i.e. pointing at the next start code.
// Return true if a NALU has been found.
// If a NALU is found:
// - its size in bytes is returned in |*nalu_size| and includes
// the start code as well as the trailing zero bits.
// - the size in bytes of the start code is returned in |*start_code_size|.
bool LocateNALU(off_t* nalu_size, off_t* start_code_size);
// Exp-Golomb code parsing as specified in chapter 9.1 of the spec.
// Read one unsigned exp-Golomb code from the stream and return in |*val|.
Result ReadUE(int* val);
// Read one signed exp-Golomb code from the stream and return in |*val|.
Result ReadSE(int* val);
// Parse scaling lists (see spec).
Result ParseScalingList(int size, int* scaling_list, bool* use_default);
Result ParseSPSScalingLists(H264SPS* sps);
Result ParsePPSScalingLists(const H264SPS& sps, H264PPS* pps);
// Parse optional VUI parameters in SPS (see spec).
Result ParseVUIParameters(H264SPS* sps);
// Set |hrd_parameters_present| to true only if they are present.
Result ParseAndIgnoreHRDParameters(bool* hrd_parameters_present);
// Parse reference picture lists' modifications (see spec).
Result ParseRefPicListModifications(H264SliceHeader* shdr);
Result ParseRefPicListModification(int num_ref_idx_active_minus1,
H264ModificationOfPicNum* ref_list_mods);
// Parse prediction weight table (see spec).
Result ParsePredWeightTable(const H264SPS& sps, H264SliceHeader* shdr);
// Parse weighting factors (see spec).
Result ParseWeightingFactors(int num_ref_idx_active_minus1,
int chroma_array_type,
int luma_log2_weight_denom,
int chroma_log2_weight_denom,
H264WeightingFactors* w_facts);
// Parse decoded reference picture marking information (see spec).
Result ParseDecRefPicMarking(H264SliceHeader* shdr);
// Pointer to the current NALU in the stream.
const uint8* stream_;
// Bytes left in the stream after the current NALU.
off_t bytes_left_;
H264BitReader br_;
// PPSes and SPSes stored for future reference.
typedef std::map<int, H264SPS*> SPSById;
typedef std::map<int, H264PPS*> PPSById;
SPSById active_SPSes_;
PPSById active_PPSes_;
DISALLOW_COPY_AND_ASSIGN(H264Parser);
};
} // namespace media
#endif // MEDIA_FILTERS_H264_PARSER_H_

View File

@ -0,0 +1,72 @@
// 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 "base/command_line.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/test_data_util.h"
#include "media/filters/h264_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
TEST(H264ParserTest, StreamFileParsing) {
base::FilePath file_path = GetTestDataFilePath("test-25fps.h264");
// Number of NALUs in the test stream to be parsed.
int num_nalus = 759;
base::MemoryMappedFile stream;
ASSERT_TRUE(stream.Initialize(file_path))
<< "Couldn't open stream file: " << file_path.MaybeAsASCII();
H264Parser parser;
parser.SetStream(stream.data(), stream.length());
// Parse until the end of stream/unsupported stream/error in stream is found.
int num_parsed_nalus = 0;
while (true) {
media::H264SliceHeader shdr;
media::H264SEIMessage sei_msg;
H264NALU nalu;
H264Parser::Result res = parser.AdvanceToNextNALU(&nalu);
if (res == H264Parser::kEOStream) {
DVLOG(1) << "Number of successfully parsed NALUs before EOS: "
<< num_parsed_nalus;
ASSERT_EQ(num_nalus, num_parsed_nalus);
return;
}
ASSERT_EQ(res, H264Parser::kOk);
++num_parsed_nalus;
int id;
switch (nalu.nal_unit_type) {
case H264NALU::kIDRSlice:
case H264NALU::kNonIDRSlice:
ASSERT_EQ(parser.ParseSliceHeader(nalu, &shdr), H264Parser::kOk);
break;
case H264NALU::kSPS:
ASSERT_EQ(parser.ParseSPS(&id), H264Parser::kOk);
break;
case H264NALU::kPPS:
ASSERT_EQ(parser.ParsePPS(&id), H264Parser::kOk);
break;
case H264NALU::kSEIMessage:
ASSERT_EQ(parser.ParseSEI(&sei_msg), H264Parser::kOk);
break;
default:
// Skip unsupported NALU.
DVLOG(4) << "Skipping unsupported NALU";
break;
}
}
}
} // namespace media

View File

@ -0,0 +1,42 @@
// 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_H_
#define MEDIA_FORMATS_MP2T_ES_PARSER_H_
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
namespace media {
class StreamParserBuffer;
namespace mp2t {
class EsParser {
public:
typedef base::Callback<void(scoped_refptr<StreamParserBuffer>)> EmitBufferCB;
EsParser() {}
virtual ~EsParser() {}
// ES parsing.
// Should use kNoTimestamp when a timestamp is not valid.
virtual bool Parse(const uint8* buf, int size,
base::TimeDelta pts,
base::TimeDelta dts) = 0;
// Flush any pending buffer.
virtual void Flush() = 0;
// Reset the state of the ES parser.
virtual void Reset() = 0;
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,276 @@
// 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 "media/formats/mp2t/es_parser_adts.h"
#include <list>
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/bit_reader.h"
#include "media/base/buffers.h"
#include "media/base/channel_layout.h"
#include "media/base/stream_parser_buffer.h"
#include "media/formats/mp2t/mp2t_common.h"
#include "media/formats/mpeg/adts_constants.h"
namespace media {
static int ExtractAdtsFrameSize(const uint8* adts_header) {
return ((static_cast<int>(adts_header[5]) >> 5) |
(static_cast<int>(adts_header[4]) << 3) |
((static_cast<int>(adts_header[3]) & 0x3) << 11));
}
static size_t ExtractAdtsFrequencyIndex(const uint8* adts_header) {
return ((adts_header[2] >> 2) & 0xf);
}
static size_t ExtractAdtsChannelConfig(const uint8* adts_header) {
return (((adts_header[3] >> 6) & 0x3) |
((adts_header[2] & 0x1) << 2));
}
// Return true if buf corresponds to an ADTS syncword.
// |buf| size must be at least 2.
static bool isAdtsSyncWord(const uint8* buf) {
return (buf[0] == 0xff) && ((buf[1] & 0xf6) == 0xf0);
}
// Look for an ADTS syncword.
// |new_pos| returns
// - either the byte position of the ADTS frame (if found)
// - or the byte position of 1st byte that was not processed (if not found).
// In every case, the returned value in |new_pos| is such that new_pos >= pos
// |frame_sz| returns the size of the ADTS frame (if found).
// Return whether a syncword was found.
static bool LookForSyncWord(const uint8* raw_es, int raw_es_size,
int pos,
int* new_pos, int* frame_sz) {
DCHECK_GE(pos, 0);
DCHECK_LE(pos, raw_es_size);
int max_offset = raw_es_size - kADTSHeaderMinSize;
if (pos >= max_offset) {
// Do not change the position if:
// - max_offset < 0: not enough bytes to get a full header
// Since pos >= 0, this is a subcase of the next condition.
// - pos >= max_offset: might be the case after reading one full frame,
// |pos| is then incremented by the frame size and might then point
// to the end of the buffer.
*new_pos = pos;
return false;
}
for (int offset = pos; offset < max_offset; offset++) {
const uint8* cur_buf = &raw_es[offset];
if (!isAdtsSyncWord(cur_buf))
// The first 12 bits must be 1.
// The layer field (2 bits) must be set to 0.
continue;
int frame_size = ExtractAdtsFrameSize(cur_buf);
if (frame_size < kADTSHeaderMinSize) {
// Too short to be an ADTS frame.
continue;
}
// Check whether there is another frame
// |size| apart from the current one.
int remaining_size = raw_es_size - offset;
if (remaining_size >= frame_size + 2 &&
!isAdtsSyncWord(&cur_buf[frame_size])) {
continue;
}
*new_pos = offset;
*frame_sz = frame_size;
return true;
}
*new_pos = max_offset;
return false;
}
namespace mp2t {
EsParserAdts::EsParserAdts(
const NewAudioConfigCB& new_audio_config_cb,
const EmitBufferCB& emit_buffer_cb,
bool sbr_in_mimetype)
: new_audio_config_cb_(new_audio_config_cb),
emit_buffer_cb_(emit_buffer_cb),
sbr_in_mimetype_(sbr_in_mimetype) {
}
EsParserAdts::~EsParserAdts() {
}
bool EsParserAdts::Parse(const uint8* buf, int size,
base::TimeDelta pts,
base::TimeDelta dts) {
int raw_es_size;
const uint8* raw_es;
// The incoming PTS applies to the access unit that comes just after
// the beginning of |buf|.
if (pts != kNoTimestamp()) {
es_byte_queue_.Peek(&raw_es, &raw_es_size);
pts_list_.push_back(EsPts(raw_es_size, pts));
}
// Copy the input data to the ES buffer.
es_byte_queue_.Push(buf, size);
es_byte_queue_.Peek(&raw_es, &raw_es_size);
// Look for every ADTS frame in the ES buffer starting at offset = 0
int es_position = 0;
int frame_size;
while (LookForSyncWord(raw_es, raw_es_size, es_position,
&es_position, &frame_size)) {
DVLOG(LOG_LEVEL_ES)
<< "ADTS syncword @ pos=" << es_position
<< " frame_size=" << frame_size;
DVLOG(LOG_LEVEL_ES)
<< "ADTS header: "
<< base::HexEncode(&raw_es[es_position], kADTSHeaderMinSize);
// Do not process the frame if this one is a partial frame.
int remaining_size = raw_es_size - es_position;
if (frame_size > remaining_size)
break;
// Update the audio configuration if needed.
DCHECK_GE(frame_size, kADTSHeaderMinSize);
if (!UpdateAudioConfiguration(&raw_es[es_position]))
return false;
// Get the PTS & the duration of this access unit.
while (!pts_list_.empty() &&
pts_list_.front().first <= es_position) {
audio_timestamp_helper_->SetBaseTimestamp(pts_list_.front().second);
pts_list_.pop_front();
}
base::TimeDelta current_pts = audio_timestamp_helper_->GetTimestamp();
base::TimeDelta frame_duration =
audio_timestamp_helper_->GetFrameDuration(kSamplesPerAACFrame);
// Emit an audio frame.
bool is_key_frame = true;
// TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
// type and allow multiple audio tracks. See https://crbug.com/341581.
scoped_refptr<StreamParserBuffer> stream_parser_buffer =
StreamParserBuffer::CopyFrom(
&raw_es[es_position],
frame_size,
is_key_frame,
DemuxerStream::AUDIO, 0);
stream_parser_buffer->SetDecodeTimestamp(current_pts);
stream_parser_buffer->set_timestamp(current_pts);
stream_parser_buffer->set_duration(frame_duration);
emit_buffer_cb_.Run(stream_parser_buffer);
// Update the PTS of the next frame.
audio_timestamp_helper_->AddFrames(kSamplesPerAACFrame);
// Skip the current frame.
es_position += frame_size;
}
// Discard all the bytes that have been processed.
DiscardEs(es_position);
return true;
}
void EsParserAdts::Flush() {
}
void EsParserAdts::Reset() {
es_byte_queue_.Reset();
pts_list_.clear();
last_audio_decoder_config_ = AudioDecoderConfig();
}
bool EsParserAdts::UpdateAudioConfiguration(const uint8* adts_header) {
size_t frequency_index = ExtractAdtsFrequencyIndex(adts_header);
if (frequency_index >= kADTSFrequencyTableSize) {
// Frequency index 13 & 14 are reserved
// while 15 means that the frequency is explicitly written
// (not supported).
return false;
}
size_t channel_configuration = ExtractAdtsChannelConfig(adts_header);
if (channel_configuration == 0 ||
channel_configuration >= kADTSChannelLayoutTableSize) {
// TODO(damienv): Add support for inband channel configuration.
return false;
}
// TODO(damienv): support HE-AAC frequency doubling (SBR)
// based on the incoming ADTS profile.
int samples_per_second = kADTSFrequencyTable[frequency_index];
int adts_profile = (adts_header[2] >> 6) & 0x3;
// The following code is written according to ISO 14496 Part 3 Table 1.11 and
// Table 1.22. (Table 1.11 refers to the capping to 48000, Table 1.22 refers
// to SBR doubling the AAC sample rate.)
// TODO(damienv) : Extend sample rate cap to 96kHz for Level 5 content.
int extended_samples_per_second = sbr_in_mimetype_
? std::min(2 * samples_per_second, 48000)
: samples_per_second;
AudioDecoderConfig audio_decoder_config(
kCodecAAC,
kSampleFormatS16,
kADTSChannelLayoutTable[channel_configuration],
extended_samples_per_second,
NULL, 0,
false);
if (!audio_decoder_config.Matches(last_audio_decoder_config_)) {
DVLOG(1) << "Sampling frequency: " << samples_per_second;
DVLOG(1) << "Extended sampling frequency: " << extended_samples_per_second;
DVLOG(1) << "Channel config: " << channel_configuration;
DVLOG(1) << "Adts profile: " << adts_profile;
// Reset the timestamp helper to use a new time scale.
if (audio_timestamp_helper_) {
base::TimeDelta base_timestamp = audio_timestamp_helper_->GetTimestamp();
audio_timestamp_helper_.reset(
new AudioTimestampHelper(samples_per_second));
audio_timestamp_helper_->SetBaseTimestamp(base_timestamp);
} else {
audio_timestamp_helper_.reset(
new AudioTimestampHelper(samples_per_second));
}
// Audio config notification.
last_audio_decoder_config_ = audio_decoder_config;
new_audio_config_cb_.Run(audio_decoder_config);
}
return true;
}
void EsParserAdts::DiscardEs(int nbytes) {
DCHECK_GE(nbytes, 0);
if (nbytes <= 0)
return;
// Adjust the ES position of each PTS.
for (EsPtsList::iterator it = pts_list_.begin(); it != pts_list_.end(); ++it)
it->first -= nbytes;
// Discard |nbytes| of ES.
es_byte_queue_.Pop(nbytes);
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,86 @@
// 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_ADTS_H_
#define MEDIA_FORMATS_MP2T_ES_PARSER_ADTS_H_
#include <list>
#include <utility>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/byte_queue.h"
#include "media/formats/mp2t/es_parser.h"
namespace media {
class AudioTimestampHelper;
class BitReader;
class StreamParserBuffer;
}
namespace media {
namespace mp2t {
class EsParserAdts : public EsParser {
public:
typedef base::Callback<void(const AudioDecoderConfig&)> NewAudioConfigCB;
EsParserAdts(const NewAudioConfigCB& new_audio_config_cb,
const EmitBufferCB& emit_buffer_cb,
bool sbr_in_mimetype);
virtual ~EsParserAdts();
// EsParser implementation.
virtual bool Parse(const uint8* buf, int size,
base::TimeDelta pts,
base::TimeDelta dts) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual void Reset() OVERRIDE;
private:
// Used to link a PTS with a byte position in the ES stream.
typedef std::pair<int, base::TimeDelta> EsPts;
typedef std::list<EsPts> EsPtsList;
// Signal any audio configuration change (if any).
// Return false if the current audio config is not
// a supported ADTS audio config.
bool UpdateAudioConfiguration(const uint8* adts_header);
// Discard some bytes from the ES stream.
void DiscardEs(int nbytes);
// Callbacks:
// - to signal a new audio configuration,
// - to send ES buffers.
NewAudioConfigCB new_audio_config_cb_;
EmitBufferCB emit_buffer_cb_;
// True when AAC SBR extension is signalled in the mimetype
// (mp4a.40.5 in the codecs parameter).
bool sbr_in_mimetype_;
// Bytes of the ES stream that have not been emitted yet.
ByteQueue es_byte_queue_;
// List of PTS associated with a position in the ES stream.
EsPtsList pts_list_;
// Interpolated PTS for frames that don't have one.
scoped_ptr<AudioTimestampHelper> audio_timestamp_helper_;
// Last audio config.
AudioDecoderConfig last_audio_decoder_config_;
DISALLOW_COPY_AND_ASSIGN(EsParserAdts);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,332 @@
// 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 "media/formats/mp2t/es_parser_h264.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/buffers.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/video_frame.h"
#include "media/filters/h264_parser.h"
#include "media/formats/common/offset_byte_queue.h"
#include "media/formats/mp2t/mp2t_common.h"
#include "ui/gfx/rect.h"
#include "ui/gfx/size.h"
namespace media {
namespace mp2t {
// An AUD NALU is at least 4 bytes:
// 3 bytes for the start code + 1 byte for the NALU type.
const int kMinAUDSize = 4;
EsParserH264::EsParserH264(
const NewVideoConfigCB& new_video_config_cb,
const EmitBufferCB& emit_buffer_cb)
: new_video_config_cb_(new_video_config_cb),
emit_buffer_cb_(emit_buffer_cb),
es_queue_(new media::OffsetByteQueue()),
h264_parser_(new H264Parser()),
current_access_unit_pos_(0),
next_access_unit_pos_(0) {
}
EsParserH264::~EsParserH264() {
}
bool EsParserH264::Parse(const uint8* buf, int size,
base::TimeDelta pts,
base::TimeDelta 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, TimingDesc>(es_queue_->tail(), timing_desc));
}
// Add the incoming bytes to the ES queue.
es_queue_->Push(buf, size);
return ParseInternal();
}
void EsParserH264::Flush() {
DVLOG(1) << "EsParserH264::Flush";
if (!FindAUD(&current_access_unit_pos_))
return;
// Simulate an additional AUD to force emitting the last access unit
// which is assumed to be complete at this point.
uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 };
es_queue_->Push(aud, sizeof(aud));
ParseInternal();
}
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_ = VideoDecoderConfig();
}
bool EsParserH264::FindAUD(int64* stream_pos) {
while (true) {
const uint8* es;
int size;
es_queue_->PeekAt(*stream_pos, &es, &size);
// Find a start code and move the stream to the start code parser position.
off_t start_code_offset;
off_t start_code_size;
bool start_code_found = H264Parser::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 >= size)
return false;
// Exit the parser loop when an AUD is found.
// Note: NALU header for an AUD:
// - nal_ref_idc must be 0
// - nal_unit_type must be H264NALU::kAUD
if (es[start_code_offset + start_code_size] == H264NALU::kAUD)
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(&current_access_unit_pos_);
es_queue_->Trim(current_access_unit_pos_);
if (next_access_unit_pos_ < current_access_unit_pos_)
next_access_unit_pos_ = current_access_unit_pos_;
// Resume parsing later if no AUD was found.
if (!aud_found)
return true;
// Find the next AUD to make sure we have a complete access unit.
if (next_access_unit_pos_ < current_access_unit_pos_ + kMinAUDSize) {
next_access_unit_pos_ = current_access_unit_pos_ + kMinAUDSize;
DCHECK_LE(next_access_unit_pos_, es_queue_->tail());
}
if (!FindAUD(&next_access_unit_pos_))
return true;
// At this point, we know we have a full access unit.
bool is_key_frame = false;
int pps_id_for_access_unit = -1;
const uint8* es;
int size;
es_queue_->PeekAt(current_access_unit_pos_, &es, &size);
int access_unit_size = base::checked_cast<int, int64>(
next_access_unit_pos_ - current_access_unit_pos_);
DCHECK_LE(access_unit_size, size);
h264_parser_->SetStream(es, access_unit_size);
while (true) {
bool is_eos = false;
H264NALU nalu;
switch (h264_parser_->AdvanceToNextNALU(&nalu)) {
case H264Parser::kOk:
break;
case H264Parser::kInvalidStream:
case H264Parser::kUnsupportedStream:
return false;
case H264Parser::kEOStream:
is_eos = true;
break;
}
if (is_eos)
break;
switch (nalu.nal_unit_type) {
case H264NALU::kAUD: {
DVLOG(LOG_LEVEL_ES) << "NALU: AUD";
break;
}
case H264NALU::kSPS: {
DVLOG(LOG_LEVEL_ES) << "NALU: SPS";
int sps_id;
if (h264_parser_->ParseSPS(&sps_id) != H264Parser::kOk)
return false;
break;
}
case H264NALU::kPPS: {
DVLOG(LOG_LEVEL_ES) << "NALU: PPS";
int pps_id;
if (h264_parser_->ParsePPS(&pps_id) != H264Parser::kOk)
return false;
break;
}
case H264NALU::kIDRSlice:
case H264NALU::kNonIDRSlice: {
is_key_frame = (nalu.nal_unit_type == H264NALU::kIDRSlice);
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.
// TODO(damienv): Should be able to differentiate a missing SPS/PPS
// from a slice header parsing error.
if (last_video_decoder_config_.IsValidConfig())
return false;
} else {
pps_id_for_access_unit = shdr.pic_parameter_set_id;
}
break;
}
default: {
DVLOG(LOG_LEVEL_ES) << "NALU: " << nalu.nal_unit_type;
}
}
}
// 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 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;
// 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_.IsValidConfig())
return false;
} else {
const H264SPS* sps = h264_parser_->GetSPS(pps->seq_parameter_set_id);
if (!sps)
return false;
RCHECK(UpdateVideoDecoderConfig(sps));
}
// Emit a frame.
DVLOG(LOG_LEVEL_ES) << "Emit frame: stream_pos=" << current_access_unit_pos_
<< " size=" << access_unit_size;
int es_size;
const uint8* es;
es_queue_->PeekAt(current_access_unit_pos_, &es, &es_size);
CHECK_GE(es_size, access_unit_size);
// TODO(wolenetz/acolwell): Validate and use a common cross-parser TrackId
// type and allow multiple video tracks. See https://crbug.com/341581.
scoped_refptr<StreamParserBuffer> stream_parser_buffer =
StreamParserBuffer::CopyFrom(
es,
access_unit_size,
is_key_frame,
DemuxerStream::VIDEO,
0);
stream_parser_buffer->SetDecodeTimestamp(current_timing_desc.dts);
stream_parser_buffer->set_timestamp(current_timing_desc.pts);
emit_buffer_cb_.Run(stream_parser_buffer);
return true;
}
bool EsParserH264::UpdateVideoDecoderConfig(const H264SPS* sps) {
// Set the SAR to 1 when not specified in the H264 stream.
int sar_width = (sps->sar_width == 0) ? 1 : sps->sar_width;
int sar_height = (sps->sar_height == 0) ? 1 : sps->sar_height;
// TODO(damienv): a MAP unit can be either 16 or 32 pixels.
// although it's 16 pixels for progressive non MBAFF frames.
gfx::Size coded_size((sps->pic_width_in_mbs_minus1 + 1) * 16,
(sps->pic_height_in_map_units_minus1 + 1) * 16);
gfx::Rect visible_rect(
sps->frame_crop_left_offset,
sps->frame_crop_top_offset,
(coded_size.width() - sps->frame_crop_right_offset) -
sps->frame_crop_left_offset,
(coded_size.height() - sps->frame_crop_bottom_offset) -
sps->frame_crop_top_offset);
if (visible_rect.width() <= 0 || visible_rect.height() <= 0)
return false;
gfx::Size natural_size(
(visible_rect.width() * sar_width) / sar_height,
visible_rect.height());
if (natural_size.width() == 0)
return false;
VideoDecoderConfig video_decoder_config(
kCodecH264,
VIDEO_CODEC_PROFILE_UNKNOWN,
VideoFrame::YV12,
coded_size,
visible_rect,
natural_size,
NULL, 0,
false);
if (!video_decoder_config.Matches(last_video_decoder_config_)) {
DVLOG(1) << "Profile IDC: " << sps->profile_idc;
DVLOG(1) << "Level IDC: " << sps->level_idc;
DVLOG(1) << "Pic width: " << coded_size.width();
DVLOG(1) << "Pic height: " << coded_size.height();
DVLOG(1) << "log2_max_frame_num_minus4: "
<< sps->log2_max_frame_num_minus4;
DVLOG(1) << "SAR: width=" << sps->sar_width
<< " height=" << sps->sar_height;
last_video_decoder_config_ = video_decoder_config;
new_video_config_cb_.Run(video_decoder_config);
}
return true;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,98 @@
// 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_H264_H_
#define MEDIA_FORMATS_MP2T_ES_PARSER_H264_H_
#include <list>
#include <utility>
#include "base/basictypes.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "base/time/time.h"
#include "media/base/media_export.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/es_parser.h"
namespace media {
class H264Parser;
struct H264SPS;
class OffsetByteQueue;
}
namespace media {
namespace mp2t {
// Remark:
// In this h264 parser, frame splitting is based on AUD nals.
// 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 MEDIA_EXPORT EsParserH264 : NON_EXPORTED_BASE(public EsParser) {
public:
typedef base::Callback<void(const VideoDecoderConfig&)> NewVideoConfigCB;
EsParserH264(const NewVideoConfigCB& new_video_config_cb,
const EmitBufferCB& emit_buffer_cb);
virtual ~EsParserH264();
// EsParser implementation.
virtual bool Parse(const uint8* buf, int size,
base::TimeDelta pts,
base::TimeDelta dts) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual void Reset() OVERRIDE;
private:
struct TimingDesc {
base::TimeDelta dts;
base::TimeDelta 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* 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 access_unit_pos, int access_unit_size,
bool is_key_frame, int pps_id);
// Update the video decoder config based on an H264 SPS.
// Return true if successful.
bool UpdateVideoDecoderConfig(const H264SPS* sps);
// Callbacks to pass the stream configuration and the frames.
NewVideoConfigCB new_video_config_cb_;
EmitBufferCB emit_buffer_cb_;
// Bytes of the ES stream that have not been emitted yet.
scoped_ptr<media::OffsetByteQueue> es_queue_;
std::list<std::pair<int64, 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 current_access_unit_pos_;
int64 next_access_unit_pos_;
// Last video decoder config.
VideoDecoderConfig last_video_decoder_config_;
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,261 @@
// 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 <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/test_data_util.h"
#include "media/filters/h264_parser.h"
#include "media/formats/mp2t/es_parser_h264.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
class VideoDecoderConfig;
namespace mp2t {
namespace {
struct Packet {
// Offset in the stream.
size_t offset;
// Size of the packet.
size_t size;
};
// Compute the size of each packet assuming packets are given in stream order
// and the last packet covers the end of the stream.
void ComputePacketSize(std::vector<Packet>& packets, size_t stream_size) {
for (size_t k = 0; k < packets.size() - 1; k++) {
DCHECK_GE(packets[k + 1].offset, packets[k].offset);
packets[k].size = packets[k + 1].offset - packets[k].offset;
}
packets[packets.size() - 1].size =
stream_size - packets[packets.size() - 1].offset;
}
// Get the offset of the start of each access unit.
// This function assumes there is only one slice per access unit.
// This is a very simplified access unit segmenter that is good
// enough for unit tests.
std::vector<Packet> GetAccessUnits(const uint8* stream, size_t stream_size) {
std::vector<Packet> access_units;
bool start_access_unit = true;
// In a first pass, retrieve the offsets of all access units.
size_t offset = 0;
while (true) {
// Find the next start code.
off_t relative_offset = 0;
off_t start_code_size = 0;
bool success = H264Parser::FindStartCode(
&stream[offset], stream_size - offset,
&relative_offset, &start_code_size);
if (!success)
break;
offset += relative_offset;
if (start_access_unit) {
Packet cur_access_unit;
cur_access_unit.offset = offset;
access_units.push_back(cur_access_unit);
start_access_unit = false;
}
// Get the NALU type.
offset += start_code_size;
if (offset >= stream_size)
break;
int nal_unit_type = stream[offset] & 0x1f;
// We assume there is only one slice per access unit.
if (nal_unit_type == H264NALU::kIDRSlice ||
nal_unit_type == H264NALU::kNonIDRSlice) {
start_access_unit = true;
}
}
ComputePacketSize(access_units, stream_size);
return access_units;
}
// Append an AUD NALU at the beginning of each access unit
// needed for streams which do not already have AUD NALUs.
void AppendAUD(
const uint8* stream, size_t stream_size,
const std::vector<Packet>& access_units,
std::vector<uint8>& stream_with_aud,
std::vector<Packet>& access_units_with_aud) {
uint8 aud[] = { 0x00, 0x00, 0x01, 0x09 };
stream_with_aud.resize(stream_size + access_units.size() * sizeof(aud));
access_units_with_aud.resize(access_units.size());
size_t offset = 0;
for (size_t k = 0; k < access_units.size(); k++) {
access_units_with_aud[k].offset = offset;
access_units_with_aud[k].size = access_units[k].size + sizeof(aud);
memcpy(&stream_with_aud[offset], aud, sizeof(aud));
offset += sizeof(aud);
memcpy(&stream_with_aud[offset],
&stream[access_units[k].offset], access_units[k].size);
offset += access_units[k].size;
}
}
} // namespace
class EsParserH264Test : public testing::Test {
public:
EsParserH264Test() : buffer_count_(0) {
}
void LoadStream(const char* filename);
void ProcessPesPackets(const std::vector<Packet>& pes_packets);
void EmitBuffer(scoped_refptr<StreamParserBuffer> buffer) {
buffer_count_++;
}
void NewVideoConfig(const VideoDecoderConfig& config) {
}
size_t buffer_count() const { return buffer_count_; }
// Stream with AUD NALUs.
std::vector<uint8> stream_;
// Access units of the stream with AUD NALUs.
std::vector<Packet> access_units_;
protected:
size_t buffer_count_;
};
void EsParserH264Test::LoadStream(const char* filename) {
base::FilePath file_path = GetTestDataFilePath(filename);
base::MemoryMappedFile stream_without_aud;
ASSERT_TRUE(stream_without_aud.Initialize(file_path))
<< "Couldn't open stream file: " << file_path.MaybeAsASCII();
// The input file does not have AUDs.
std::vector<Packet> access_units_without_aud = GetAccessUnits(
stream_without_aud.data(), stream_without_aud.length());
ASSERT_GT(access_units_without_aud.size(), 0u);
AppendAUD(stream_without_aud.data(), stream_without_aud.length(),
access_units_without_aud,
stream_, access_units_);
}
void EsParserH264Test::ProcessPesPackets(
const std::vector<Packet>& pes_packets) {
EsParserH264 es_parser(
base::Bind(&EsParserH264Test::NewVideoConfig, base::Unretained(this)),
base::Bind(&EsParserH264Test::EmitBuffer, base::Unretained(this)));
size_t au_idx = 0;
for (size_t k = 0; k < pes_packets.size(); k++) {
size_t cur_pes_offset = pes_packets[k].offset;
size_t cur_pes_size = pes_packets[k].size;
// Update the access unit the PES belongs to from a timing point of view.
while (au_idx < access_units_.size() - 1 &&
cur_pes_offset <= access_units_[au_idx + 1].offset &&
cur_pes_offset + cur_pes_size > access_units_[au_idx + 1].offset) {
au_idx++;
}
// Check whether the PES packet includes the start of an access unit.
// The timings are relevant only in this case.
base::TimeDelta pts = kNoTimestamp();
base::TimeDelta dts = kNoTimestamp();
if (cur_pes_offset <= access_units_[au_idx].offset &&
cur_pes_offset + cur_pes_size > access_units_[au_idx].offset) {
pts = base::TimeDelta::FromMilliseconds(au_idx * 40u);
}
ASSERT_TRUE(
es_parser.Parse(&stream_[cur_pes_offset], cur_pes_size, pts, dts));
}
es_parser.Flush();
}
TEST_F(EsParserH264Test, OneAccessUnitPerPes) {
LoadStream("bear.h264");
// One to one equivalence between PES packets and access units.
std::vector<Packet> pes_packets(access_units_);
// Process each PES packet.
ProcessPesPackets(pes_packets);
ASSERT_EQ(buffer_count(), access_units_.size());
}
TEST_F(EsParserH264Test, NonAlignedPesPacket) {
LoadStream("bear.h264");
// Generate the PES packets.
std::vector<Packet> pes_packets;
Packet cur_pes_packet;
cur_pes_packet.offset = 0;
for (size_t k = 0; k < access_units_.size(); k++) {
pes_packets.push_back(cur_pes_packet);
// The current PES packet includes the remaining bytes of the previous
// access unit and some bytes of the current access unit
// (487 bytes in this unit test but no more than the current access unit
// size).
cur_pes_packet.offset = access_units_[k].offset +
std::min<size_t>(487u, access_units_[k].size);
}
ComputePacketSize(pes_packets, stream_.size());
// Process each PES packet.
ProcessPesPackets(pes_packets);
ASSERT_EQ(buffer_count(), access_units_.size());
}
TEST_F(EsParserH264Test, SeveralPesPerAccessUnit) {
LoadStream("bear.h264");
// Get the minimum size of an access unit.
size_t min_access_unit_size = stream_.size();
for (size_t k = 0; k < access_units_.size(); k++) {
if (min_access_unit_size >= access_units_[k].size)
min_access_unit_size = access_units_[k].size;
}
// Use a small PES packet size or the minimum access unit size
// if it is even smaller.
size_t pes_size = 512;
if (min_access_unit_size < pes_size)
pes_size = min_access_unit_size;
std::vector<Packet> pes_packets;
Packet cur_pes_packet;
cur_pes_packet.offset = 0;
while (cur_pes_packet.offset < stream_.size()) {
pes_packets.push_back(cur_pes_packet);
cur_pes_packet.offset += pes_size;
}
ComputePacketSize(pes_packets, stream_.size());
// Process each PES packet.
ProcessPesPackets(pes_packets);
ASSERT_EQ(buffer_count(), access_units_.size());
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,21 @@
// 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_MP2T_COMMON_H_
#define MEDIA_FORMATS_MP2T_MP2T_COMMON_H_
#define LOG_LEVEL_TS 5
#define LOG_LEVEL_PES 4
#define LOG_LEVEL_ES 3
#define RCHECK(x) \
do { \
if (!(x)) { \
DLOG(WARNING) << "Failure while parsing Mpeg2TS: " << #x; \
return false; \
} \
} while (0)
#endif

View File

@ -0,0 +1,622 @@
// 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 "media/formats/mp2t/mp2t_stream_parser.h"
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/stl_util.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/buffers.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/text_track_config.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/es_parser.h"
#include "media/formats/mp2t/es_parser_adts.h"
#include "media/formats/mp2t/es_parser_h264.h"
#include "media/formats/mp2t/mp2t_common.h"
#include "media/formats/mp2t/ts_packet.h"
#include "media/formats/mp2t/ts_section.h"
#include "media/formats/mp2t/ts_section_pat.h"
#include "media/formats/mp2t/ts_section_pes.h"
#include "media/formats/mp2t/ts_section_pmt.h"
namespace media {
namespace mp2t {
enum StreamType {
// ISO-13818.1 / ITU H.222 Table 2.34 "Stream type assignments"
kStreamTypeMpeg1Audio = 0x3,
kStreamTypeAAC = 0xf,
kStreamTypeAVC = 0x1b,
};
class PidState {
public:
enum PidType {
kPidPat,
kPidPmt,
kPidAudioPes,
kPidVideoPes,
};
PidState(int pid, PidType pid_tyoe,
scoped_ptr<TsSection> section_parser);
// Extract the content of the TS packet and parse it.
// Return true if successful.
bool PushTsPacket(const TsPacket& ts_packet);
// Flush the PID state (possibly emitting some pending frames)
// and reset its state.
void Flush();
// Enable/disable the PID.
// Disabling a PID will reset its state and ignore any further incoming TS
// packets.
void Enable();
void Disable();
bool IsEnabled() const;
PidType pid_type() const { return pid_type_; }
private:
void ResetState();
int pid_;
PidType pid_type_;
scoped_ptr<TsSection> section_parser_;
bool enable_;
int continuity_counter_;
};
PidState::PidState(int pid, PidType pid_type,
scoped_ptr<TsSection> section_parser)
: pid_(pid),
pid_type_(pid_type),
section_parser_(section_parser.Pass()),
enable_(false),
continuity_counter_(-1) {
DCHECK(section_parser_);
}
bool PidState::PushTsPacket(const TsPacket& ts_packet) {
DCHECK_EQ(ts_packet.pid(), pid_);
// The current PID is not part of the PID filter,
// just discard the incoming TS packet.
if (!enable_)
return true;
int expected_continuity_counter = (continuity_counter_ + 1) % 16;
if (continuity_counter_ >= 0 &&
ts_packet.continuity_counter() != expected_continuity_counter) {
DVLOG(1) << "TS discontinuity detected for pid: " << pid_;
return false;
}
bool status = section_parser_->Parse(
ts_packet.payload_unit_start_indicator(),
ts_packet.payload(),
ts_packet.payload_size());
// At the minimum, when parsing failed, auto reset the section parser.
// Components that use the StreamParser can take further action if needed.
if (!status) {
DVLOG(1) << "Parsing failed for pid = " << pid_;
ResetState();
}
return status;
}
void PidState::Flush() {
section_parser_->Flush();
ResetState();
}
void PidState::Enable() {
enable_ = true;
}
void PidState::Disable() {
if (!enable_)
return;
ResetState();
enable_ = false;
}
bool PidState::IsEnabled() const {
return enable_;
}
void PidState::ResetState() {
section_parser_->Reset();
continuity_counter_ = -1;
}
Mp2tStreamParser::BufferQueueWithConfig::BufferQueueWithConfig(
bool is_cfg_sent,
const AudioDecoderConfig& audio_cfg,
const VideoDecoderConfig& video_cfg)
: is_config_sent(is_cfg_sent),
audio_config(audio_cfg),
video_config(video_cfg) {
}
Mp2tStreamParser::BufferQueueWithConfig::~BufferQueueWithConfig() {
}
Mp2tStreamParser::Mp2tStreamParser(bool sbr_in_mimetype)
: sbr_in_mimetype_(sbr_in_mimetype),
selected_audio_pid_(-1),
selected_video_pid_(-1),
is_initialized_(false),
segment_started_(false),
first_video_frame_in_segment_(true) {
}
Mp2tStreamParser::~Mp2tStreamParser() {
STLDeleteValues(&pids_);
}
void Mp2tStreamParser::Init(
const InitCB& init_cb,
const NewConfigCB& config_cb,
const NewBuffersCB& new_buffers_cb,
bool /* ignore_text_tracks */ ,
const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb,
const LogCB& log_cb) {
DCHECK(!is_initialized_);
DCHECK(init_cb_.is_null());
DCHECK(!init_cb.is_null());
DCHECK(!config_cb.is_null());
DCHECK(!new_buffers_cb.is_null());
DCHECK(!need_key_cb.is_null());
DCHECK(!end_of_segment_cb.is_null());
init_cb_ = init_cb;
config_cb_ = config_cb;
new_buffers_cb_ = new_buffers_cb;
need_key_cb_ = need_key_cb;
new_segment_cb_ = new_segment_cb;
end_of_segment_cb_ = end_of_segment_cb;
log_cb_ = log_cb;
}
void Mp2tStreamParser::Flush() {
DVLOG(1) << "Mp2tStreamParser::Flush";
// Flush the buffers and reset the pids.
for (std::map<int, PidState*>::iterator it = pids_.begin();
it != pids_.end(); ++it) {
DVLOG(1) << "Flushing PID: " << it->first;
PidState* pid_state = it->second;
pid_state->Flush();
delete pid_state;
}
pids_.clear();
EmitRemainingBuffers();
buffer_queue_chain_.clear();
// End of the segment.
// Note: does not need to invoke |end_of_segment_cb_| since flushing the
// stream parser already involves the end of the current segment.
segment_started_ = false;
first_video_frame_in_segment_ = true;
// Remove any bytes left in the TS buffer.
// (i.e. any partial TS packet => less than 188 bytes).
ts_byte_queue_.Reset();
// Reset the selected PIDs.
selected_audio_pid_ = -1;
selected_video_pid_ = -1;
}
bool Mp2tStreamParser::Parse(const uint8* buf, int size) {
DVLOG(1) << "Mp2tStreamParser::Parse size=" << size;
// Add the data to the parser state.
ts_byte_queue_.Push(buf, size);
while (true) {
const uint8* ts_buffer;
int ts_buffer_size;
ts_byte_queue_.Peek(&ts_buffer, &ts_buffer_size);
if (ts_buffer_size < TsPacket::kPacketSize)
break;
// Synchronization.
int skipped_bytes = TsPacket::Sync(ts_buffer, ts_buffer_size);
if (skipped_bytes > 0) {
DVLOG(1) << "Packet not aligned on a TS syncword:"
<< " skipped_bytes=" << skipped_bytes;
ts_byte_queue_.Pop(skipped_bytes);
continue;
}
// Parse the TS header, skipping 1 byte if the header is invalid.
scoped_ptr<TsPacket> ts_packet(TsPacket::Parse(ts_buffer, ts_buffer_size));
if (!ts_packet) {
DVLOG(1) << "Error: invalid TS packet";
ts_byte_queue_.Pop(1);
continue;
}
DVLOG(LOG_LEVEL_TS)
<< "Processing PID=" << ts_packet->pid()
<< " start_unit=" << ts_packet->payload_unit_start_indicator();
// Parse the section.
std::map<int, PidState*>::iterator it = pids_.find(ts_packet->pid());
if (it == pids_.end() &&
ts_packet->pid() == TsSection::kPidPat) {
// Create the PAT state here if needed.
scoped_ptr<TsSection> pat_section_parser(
new TsSectionPat(
base::Bind(&Mp2tStreamParser::RegisterPmt,
base::Unretained(this))));
scoped_ptr<PidState> pat_pid_state(
new PidState(ts_packet->pid(), PidState::kPidPat,
pat_section_parser.Pass()));
pat_pid_state->Enable();
it = pids_.insert(
std::pair<int, PidState*>(ts_packet->pid(),
pat_pid_state.release())).first;
}
if (it != pids_.end()) {
if (!it->second->PushTsPacket(*ts_packet))
return false;
} else {
DVLOG(LOG_LEVEL_TS) << "Ignoring TS packet for pid: " << ts_packet->pid();
}
// Go to the next packet.
ts_byte_queue_.Pop(TsPacket::kPacketSize);
}
RCHECK(FinishInitializationIfNeeded());
// Emit the A/V buffers that kept accumulating during TS parsing.
return EmitRemainingBuffers();
}
void Mp2tStreamParser::RegisterPmt(int program_number, int pmt_pid) {
DVLOG(1) << "RegisterPmt:"
<< " program_number=" << program_number
<< " pmt_pid=" << pmt_pid;
// Only one TS program is allowed. Ignore the incoming program map table,
// if there is already one registered.
for (std::map<int, PidState*>::iterator it = pids_.begin();
it != pids_.end(); ++it) {
PidState* pid_state = it->second;
if (pid_state->pid_type() == PidState::kPidPmt) {
DVLOG_IF(1, pmt_pid != it->first) << "More than one program is defined";
return;
}
}
// Create the PMT state here if needed.
DVLOG(1) << "Create a new PMT parser";
scoped_ptr<TsSection> pmt_section_parser(
new TsSectionPmt(
base::Bind(&Mp2tStreamParser::RegisterPes,
base::Unretained(this), pmt_pid)));
scoped_ptr<PidState> pmt_pid_state(
new PidState(pmt_pid, PidState::kPidPmt, pmt_section_parser.Pass()));
pmt_pid_state->Enable();
pids_.insert(std::pair<int, PidState*>(pmt_pid, pmt_pid_state.release()));
}
void Mp2tStreamParser::RegisterPes(int pmt_pid,
int pes_pid,
int stream_type) {
// TODO(damienv): check there is no mismatch if the entry already exists.
DVLOG(1) << "RegisterPes:"
<< " pes_pid=" << pes_pid
<< " stream_type=" << std::hex << stream_type << std::dec;
std::map<int, PidState*>::iterator it = pids_.find(pes_pid);
if (it != pids_.end())
return;
// Create a stream parser corresponding to the stream type.
bool is_audio = false;
scoped_ptr<EsParser> es_parser;
if (stream_type == kStreamTypeAVC) {
es_parser.reset(
new EsParserH264(
base::Bind(&Mp2tStreamParser::OnVideoConfigChanged,
base::Unretained(this),
pes_pid),
base::Bind(&Mp2tStreamParser::OnEmitVideoBuffer,
base::Unretained(this),
pes_pid)));
} else if (stream_type == kStreamTypeAAC) {
es_parser.reset(
new EsParserAdts(
base::Bind(&Mp2tStreamParser::OnAudioConfigChanged,
base::Unretained(this),
pes_pid),
base::Bind(&Mp2tStreamParser::OnEmitAudioBuffer,
base::Unretained(this),
pes_pid),
sbr_in_mimetype_));
is_audio = true;
} else {
return;
}
// Create the PES state here.
DVLOG(1) << "Create a new PES state";
scoped_ptr<TsSection> pes_section_parser(
new TsSectionPes(es_parser.Pass()));
PidState::PidType pid_type =
is_audio ? PidState::kPidAudioPes : PidState::kPidVideoPes;
scoped_ptr<PidState> pes_pid_state(
new PidState(pes_pid, pid_type, pes_section_parser.Pass()));
pids_.insert(std::pair<int, PidState*>(pes_pid, pes_pid_state.release()));
// A new PES pid has been added, the PID filter might change.
UpdatePidFilter();
}
void Mp2tStreamParser::UpdatePidFilter() {
// Applies the HLS rule to select the default audio/video PIDs:
// select the audio/video streams with the lowest PID.
// TODO(damienv): this can be changed when the StreamParser interface
// supports multiple audio/video streams.
PidMap::iterator lowest_audio_pid = pids_.end();
PidMap::iterator lowest_video_pid = pids_.end();
for (PidMap::iterator it = pids_.begin(); it != pids_.end(); ++it) {
int pid = it->first;
PidState* pid_state = it->second;
if (pid_state->pid_type() == PidState::kPidAudioPes &&
(lowest_audio_pid == pids_.end() || pid < lowest_audio_pid->first))
lowest_audio_pid = it;
if (pid_state->pid_type() == PidState::kPidVideoPes &&
(lowest_video_pid == pids_.end() || pid < lowest_video_pid->first))
lowest_video_pid = it;
}
// Enable both the lowest audio and video PIDs.
if (lowest_audio_pid != pids_.end()) {
DVLOG(1) << "Enable audio pid: " << lowest_audio_pid->first;
lowest_audio_pid->second->Enable();
selected_audio_pid_ = lowest_audio_pid->first;
}
if (lowest_video_pid != pids_.end()) {
DVLOG(1) << "Enable video pid: " << lowest_video_pid->first;
lowest_video_pid->second->Enable();
selected_video_pid_ = lowest_video_pid->first;
}
// Disable all the other audio and video PIDs.
for (PidMap::iterator it = pids_.begin(); it != pids_.end(); ++it) {
PidState* pid_state = it->second;
if (it != lowest_audio_pid && it != lowest_video_pid &&
(pid_state->pid_type() == PidState::kPidAudioPes ||
pid_state->pid_type() == PidState::kPidVideoPes))
pid_state->Disable();
}
}
void Mp2tStreamParser::OnVideoConfigChanged(
int pes_pid,
const VideoDecoderConfig& video_decoder_config) {
DVLOG(1) << "OnVideoConfigChanged for pid=" << pes_pid;
DCHECK_EQ(pes_pid, selected_video_pid_);
DCHECK(video_decoder_config.IsValidConfig());
// Create a new entry in |buffer_queue_chain_| with the updated configs.
BufferQueueWithConfig buffer_queue_with_config(
false,
buffer_queue_chain_.empty()
? AudioDecoderConfig() : buffer_queue_chain_.back().audio_config,
video_decoder_config);
buffer_queue_chain_.push_back(buffer_queue_with_config);
// Replace any non valid config with the 1st valid entry.
// This might happen if there was no available config before.
for (std::list<BufferQueueWithConfig>::iterator it =
buffer_queue_chain_.begin(); it != buffer_queue_chain_.end(); ++it) {
if (it->video_config.IsValidConfig())
break;
it->video_config = video_decoder_config;
}
}
void Mp2tStreamParser::OnAudioConfigChanged(
int pes_pid,
const AudioDecoderConfig& audio_decoder_config) {
DVLOG(1) << "OnAudioConfigChanged for pid=" << pes_pid;
DCHECK_EQ(pes_pid, selected_audio_pid_);
DCHECK(audio_decoder_config.IsValidConfig());
// Create a new entry in |buffer_queue_chain_| with the updated configs.
BufferQueueWithConfig buffer_queue_with_config(
false,
audio_decoder_config,
buffer_queue_chain_.empty()
? VideoDecoderConfig() : buffer_queue_chain_.back().video_config);
buffer_queue_chain_.push_back(buffer_queue_with_config);
// Replace any non valid config with the 1st valid entry.
// This might happen if there was no available config before.
for (std::list<BufferQueueWithConfig>::iterator it =
buffer_queue_chain_.begin(); it != buffer_queue_chain_.end(); ++it) {
if (it->audio_config.IsValidConfig())
break;
it->audio_config = audio_decoder_config;
}
}
bool Mp2tStreamParser::FinishInitializationIfNeeded() {
// Nothing to be done if already initialized.
if (is_initialized_)
return true;
// Wait for more data to come to finish initialization.
if (buffer_queue_chain_.empty())
return true;
// Wait for more data to come if one of the config is not available.
BufferQueueWithConfig& queue_with_config = buffer_queue_chain_.front();
if (selected_audio_pid_ > 0 &&
!queue_with_config.audio_config.IsValidConfig())
return true;
if (selected_video_pid_ > 0 &&
!queue_with_config.video_config.IsValidConfig())
return true;
// Pass the config before invoking the initialization callback.
RCHECK(config_cb_.Run(queue_with_config.audio_config,
queue_with_config.video_config,
TextTrackConfigMap()));
queue_with_config.is_config_sent = true;
// For Mpeg2 TS, the duration is not known.
DVLOG(1) << "Mpeg2TS stream parser initialization done";
init_cb_.Run(true, kInfiniteDuration(), false);
is_initialized_ = true;
return true;
}
void Mp2tStreamParser::OnEmitAudioBuffer(
int pes_pid,
scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
DCHECK_EQ(pes_pid, selected_audio_pid_);
DVLOG(LOG_LEVEL_ES)
<< "OnEmitAudioBuffer: "
<< " size="
<< stream_parser_buffer->data_size()
<< " dts="
<< stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
<< " pts="
<< stream_parser_buffer->timestamp().InMilliseconds();
stream_parser_buffer->set_timestamp(
stream_parser_buffer->timestamp() - time_offset_);
stream_parser_buffer->SetDecodeTimestamp(
stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
// Ignore the incoming buffer if it is not associated with any config.
if (buffer_queue_chain_.empty()) {
DVLOG(1) << "Ignoring audio buffer with no corresponding audio config";
return;
}
buffer_queue_chain_.back().audio_queue.push_back(stream_parser_buffer);
}
void Mp2tStreamParser::OnEmitVideoBuffer(
int pes_pid,
scoped_refptr<StreamParserBuffer> stream_parser_buffer) {
DCHECK_EQ(pes_pid, selected_video_pid_);
DVLOG(LOG_LEVEL_ES)
<< "OnEmitVideoBuffer"
<< " size="
<< stream_parser_buffer->data_size()
<< " dts="
<< stream_parser_buffer->GetDecodeTimestamp().InMilliseconds()
<< " pts="
<< stream_parser_buffer->timestamp().InMilliseconds()
<< " IsKeyframe="
<< stream_parser_buffer->IsKeyframe();
stream_parser_buffer->set_timestamp(
stream_parser_buffer->timestamp() - time_offset_);
stream_parser_buffer->SetDecodeTimestamp(
stream_parser_buffer->GetDecodeTimestamp() - time_offset_);
// Ignore the incoming buffer if it is not associated with any config.
if (buffer_queue_chain_.empty()) {
DVLOG(1) << "Ignoring video buffer with no corresponding video config:"
<< " keyframe=" << stream_parser_buffer->IsKeyframe()
<< " dts="
<< stream_parser_buffer->GetDecodeTimestamp().InMilliseconds();
return;
}
// A segment cannot start with a non key frame.
// Ignore the frame if that's the case.
if (first_video_frame_in_segment_ && !stream_parser_buffer->IsKeyframe()) {
DVLOG(1) << "Ignoring non-key frame:"
<< " dts="
<< stream_parser_buffer->GetDecodeTimestamp().InMilliseconds();
return;
}
first_video_frame_in_segment_ = false;
buffer_queue_chain_.back().video_queue.push_back(stream_parser_buffer);
}
bool Mp2tStreamParser::EmitRemainingBuffers() {
DVLOG(LOG_LEVEL_ES) << "Mp2tStreamParser::EmitRemainingBuffers";
// No buffer should be sent until fully initialized.
if (!is_initialized_)
return true;
if (buffer_queue_chain_.empty())
return true;
// Keep track of the last audio and video config sent.
AudioDecoderConfig last_audio_config =
buffer_queue_chain_.back().audio_config;
VideoDecoderConfig last_video_config =
buffer_queue_chain_.back().video_config;
// Buffer emission.
while (!buffer_queue_chain_.empty()) {
// Start a segment if needed.
if (!segment_started_) {
DVLOG(1) << "Starting a new segment";
segment_started_ = true;
new_segment_cb_.Run();
}
// Update the audio and video config if needed.
BufferQueueWithConfig& queue_with_config = buffer_queue_chain_.front();
if (!queue_with_config.is_config_sent) {
if (!config_cb_.Run(queue_with_config.audio_config,
queue_with_config.video_config,
TextTrackConfigMap()))
return false;
queue_with_config.is_config_sent = true;
}
// Add buffers.
TextBufferQueueMap empty_text_map;
if (!queue_with_config.audio_queue.empty() ||
!queue_with_config.video_queue.empty()) {
if (!new_buffers_cb_.Run(queue_with_config.audio_queue,
queue_with_config.video_queue,
empty_text_map)) {
return false;
}
}
buffer_queue_chain_.pop_front();
}
// Push an empty queue with the last audio/video config
// so that buffers with the same config can be added later on.
BufferQueueWithConfig queue_with_config(
true, last_audio_config, last_video_config);
buffer_queue_chain_.push_back(queue_with_config);
return true;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,136 @@
// 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_MP2T_STREAM_PARSER_H_
#define MEDIA_FORMATS_MP2T_MP2T_STREAM_PARSER_H_
#include <list>
#include <map>
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/byte_queue.h"
#include "media/base/media_export.h"
#include "media/base/stream_parser.h"
#include "media/base/video_decoder_config.h"
namespace media {
class StreamParserBuffer;
namespace mp2t {
class PidState;
class MEDIA_EXPORT Mp2tStreamParser : public StreamParser {
public:
explicit Mp2tStreamParser(bool sbr_in_mimetype);
virtual ~Mp2tStreamParser();
// StreamParser implementation.
virtual void Init(const InitCB& init_cb,
const NewConfigCB& config_cb,
const NewBuffersCB& new_buffers_cb,
bool ignore_text_tracks,
const NeedKeyCB& need_key_cb,
const NewMediaSegmentCB& new_segment_cb,
const base::Closure& end_of_segment_cb,
const LogCB& log_cb) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual bool Parse(const uint8* buf, int size) OVERRIDE;
private:
typedef std::map<int, PidState*> PidMap;
struct BufferQueueWithConfig {
BufferQueueWithConfig(bool is_cfg_sent,
const AudioDecoderConfig& audio_cfg,
const VideoDecoderConfig& video_cfg);
~BufferQueueWithConfig();
bool is_config_sent;
AudioDecoderConfig audio_config;
StreamParser::BufferQueue audio_queue;
VideoDecoderConfig video_config;
StreamParser::BufferQueue video_queue;
};
// Callback invoked to register a Program Map Table.
// Note: Does nothing if the PID is already registered.
void RegisterPmt(int program_number, int pmt_pid);
// Callback invoked to register a PES pid.
// Possible values for |stream_type| are defined in:
// ISO-13818.1 / ITU H.222 Table 2.34 "Stream type assignments".
// |pes_pid| is part of the Program Map Table refered by |pmt_pid|.
void RegisterPes(int pmt_pid, int pes_pid, int stream_type);
// Since the StreamParser interface allows only one audio & video streams,
// an automatic PID filtering should be applied to select the audio & video
// streams.
void UpdatePidFilter();
// Callback invoked each time the audio/video decoder configuration is
// changed.
void OnVideoConfigChanged(int pes_pid,
const VideoDecoderConfig& video_decoder_config);
void OnAudioConfigChanged(int pes_pid,
const AudioDecoderConfig& audio_decoder_config);
// Invoke the initialization callback if needed.
bool FinishInitializationIfNeeded();
// Callback invoked by the ES stream parser
// to emit a new audio/video access unit.
void OnEmitAudioBuffer(
int pes_pid,
scoped_refptr<StreamParserBuffer> stream_parser_buffer);
void OnEmitVideoBuffer(
int pes_pid,
scoped_refptr<StreamParserBuffer> stream_parser_buffer);
bool EmitRemainingBuffers();
// List of callbacks.
InitCB init_cb_;
NewConfigCB config_cb_;
NewBuffersCB new_buffers_cb_;
NeedKeyCB need_key_cb_;
NewMediaSegmentCB new_segment_cb_;
base::Closure end_of_segment_cb_;
LogCB log_cb_;
// True when AAC SBR extension is signalled in the mimetype
// (mp4a.40.5 in the codecs parameter).
bool sbr_in_mimetype_;
// Bytes of the TS stream.
ByteQueue ts_byte_queue_;
// List of PIDs and their state.
PidMap pids_;
// Selected audio and video PIDs.
int selected_audio_pid_;
int selected_video_pid_;
// Pending audio & video buffers.
std::list<BufferQueueWithConfig> buffer_queue_chain_;
// Whether |init_cb_| has been invoked.
bool is_initialized_;
// Indicate whether a segment was started.
bool segment_started_;
bool first_video_frame_in_segment_;
base::TimeDelta time_offset_;
DISALLOW_COPY_AND_ASSIGN(Mp2tStreamParser);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,187 @@
// 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 <algorithm>
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/test_data_util.h"
#include "media/base/text_track_config.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp2t/mp2t_stream_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace mp2t {
class Mp2tStreamParserTest : public testing::Test {
public:
Mp2tStreamParserTest()
: audio_frame_count_(0),
video_frame_count_(0),
video_min_dts_(kNoTimestamp()),
video_max_dts_(kNoTimestamp()) {
bool has_sbr = false;
parser_.reset(new Mp2tStreamParser(has_sbr));
}
protected:
scoped_ptr<Mp2tStreamParser> parser_;
int audio_frame_count_;
int video_frame_count_;
base::TimeDelta video_min_dts_;
base::TimeDelta video_max_dts_;
bool AppendData(const uint8* data, size_t length) {
return parser_->Parse(data, length);
}
bool AppendDataInPieces(const uint8* data, size_t length, size_t piece_size) {
const uint8* start = data;
const uint8* end = data + length;
while (start < end) {
size_t append_size = std::min(piece_size,
static_cast<size_t>(end - start));
if (!AppendData(start, append_size))
return false;
start += append_size;
}
return true;
}
void OnInit(bool init_ok,
base::TimeDelta duration,
bool auto_update_timestamp_offset) {
DVLOG(1) << "OnInit: ok=" << init_ok
<< ", dur=" << duration.InMilliseconds()
<< ", autoTimestampOffset=" << auto_update_timestamp_offset;
}
bool OnNewConfig(const AudioDecoderConfig& ac,
const VideoDecoderConfig& vc,
const StreamParser::TextTrackConfigMap& tc) {
DVLOG(1) << "OnNewConfig: audio=" << ac.IsValidConfig()
<< ", video=" << vc.IsValidConfig();
return true;
}
void DumpBuffers(const std::string& label,
const StreamParser::BufferQueue& buffers) {
DVLOG(2) << "DumpBuffers: " << label << " size " << buffers.size();
for (StreamParser::BufferQueue::const_iterator buf = buffers.begin();
buf != buffers.end(); buf++) {
DVLOG(3) << " n=" << buf - buffers.begin()
<< ", size=" << (*buf)->data_size()
<< ", dur=" << (*buf)->duration().InMilliseconds();
}
}
bool OnNewBuffers(const StreamParser::BufferQueue& audio_buffers,
const StreamParser::BufferQueue& video_buffers,
const StreamParser::TextBufferQueueMap& text_map) {
DumpBuffers("audio_buffers", audio_buffers);
DumpBuffers("video_buffers", video_buffers);
audio_frame_count_ += audio_buffers.size();
video_frame_count_ += video_buffers.size();
// TODO(wolenetz/acolwell): Add text track support to more MSE parsers. See
// http://crbug.com/336926.
if (!text_map.empty())
return false;
if (video_min_dts_ == kNoTimestamp() && !video_buffers.empty())
video_min_dts_ = video_buffers.front()->GetDecodeTimestamp();
if (!video_buffers.empty()) {
video_max_dts_ = video_buffers.back()->GetDecodeTimestamp();
// Verify monotonicity.
StreamParser::BufferQueue::const_iterator it1 = video_buffers.begin();
StreamParser::BufferQueue::const_iterator it2 = ++it1;
for ( ; it2 != video_buffers.end(); ++it1, ++it2) {
if ((*it2)->GetDecodeTimestamp() < (*it1)->GetDecodeTimestamp())
return false;
}
}
return true;
}
void OnKeyNeeded(const std::string& type,
const std::vector<uint8>& init_data) {
DVLOG(1) << "OnKeyNeeded: " << init_data.size();
}
void OnNewSegment() {
DVLOG(1) << "OnNewSegment";
}
void OnEndOfSegment() {
DVLOG(1) << "OnEndOfSegment()";
}
void InitializeParser() {
parser_->Init(
base::Bind(&Mp2tStreamParserTest::OnInit,
base::Unretained(this)),
base::Bind(&Mp2tStreamParserTest::OnNewConfig,
base::Unretained(this)),
base::Bind(&Mp2tStreamParserTest::OnNewBuffers,
base::Unretained(this)),
true,
base::Bind(&Mp2tStreamParserTest::OnKeyNeeded,
base::Unretained(this)),
base::Bind(&Mp2tStreamParserTest::OnNewSegment,
base::Unretained(this)),
base::Bind(&Mp2tStreamParserTest::OnEndOfSegment,
base::Unretained(this)),
LogCB());
}
bool ParseMpeg2TsFile(const std::string& filename, int append_bytes) {
InitializeParser();
scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile(filename);
EXPECT_TRUE(AppendDataInPieces(buffer->data(),
buffer->data_size(),
append_bytes));
return true;
}
};
TEST_F(Mp2tStreamParserTest, UnalignedAppend17) {
// Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-1280x720.ts", 17);
EXPECT_EQ(video_frame_count_, 81);
parser_->Flush();
EXPECT_EQ(video_frame_count_, 82);
}
TEST_F(Mp2tStreamParserTest, UnalignedAppend512) {
// Test small, non-segment-aligned appends.
ParseMpeg2TsFile("bear-1280x720.ts", 512);
EXPECT_EQ(video_frame_count_, 81);
parser_->Flush();
EXPECT_EQ(video_frame_count_, 82);
}
TEST_F(Mp2tStreamParserTest, TimestampWrapAround) {
// "bear-1280x720_ptswraparound.ts" has been transcoded
// from bear-1280x720.mp4 by applying a time offset of 95442s
// (close to 2^33 / 90000) which results in timestamps wrap around
// in the Mpeg2 TS stream.
ParseMpeg2TsFile("bear-1280x720_ptswraparound.ts", 512);
EXPECT_EQ(video_frame_count_, 81);
EXPECT_GE(video_min_dts_, base::TimeDelta::FromSeconds(95443 - 10));
EXPECT_LE(video_max_dts_, base::TimeDelta::FromSeconds(95443 + 10));
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,215 @@
// 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 "media/formats/mp2t/ts_packet.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/bit_reader.h"
#include "media/formats/mp2t/mp2t_common.h"
namespace media {
namespace mp2t {
static const uint8 kTsHeaderSyncword = 0x47;
// static
int TsPacket::Sync(const uint8* buf, int size) {
int k = 0;
for (; k < size; k++) {
// Verify that we have 4 syncwords in a row when possible,
// this should improve synchronization robustness.
// TODO(damienv): Consider the case where there is garbage
// between TS packets.
bool is_header = true;
for (int i = 0; i < 4; i++) {
int idx = k + i * kPacketSize;
if (idx >= size)
break;
if (buf[idx] != kTsHeaderSyncword) {
DVLOG(LOG_LEVEL_TS)
<< "ByteSync" << idx << ": "
<< std::hex << static_cast<int>(buf[idx]) << std::dec;
is_header = false;
break;
}
}
if (is_header)
break;
}
DVLOG_IF(1, k != 0) << "SYNC: nbytes_skipped=" << k;
return k;
}
// static
TsPacket* TsPacket::Parse(const uint8* buf, int size) {
if (size < kPacketSize) {
DVLOG(1) << "Buffer does not hold one full TS packet:"
<< " buffer_size=" << size;
return NULL;
}
DCHECK_EQ(buf[0], kTsHeaderSyncword);
if (buf[0] != kTsHeaderSyncword) {
DVLOG(1) << "Not on a TS syncword:"
<< " buf[0]="
<< std::hex << static_cast<int>(buf[0]) << std::dec;
return NULL;
}
scoped_ptr<TsPacket> ts_packet(new TsPacket());
bool status = ts_packet->ParseHeader(buf);
if (!status) {
DVLOG(1) << "Parsing header failed";
return NULL;
}
return ts_packet.release();
}
TsPacket::TsPacket() {
}
TsPacket::~TsPacket() {
}
bool TsPacket::ParseHeader(const uint8* buf) {
BitReader bit_reader(buf, kPacketSize);
payload_ = buf;
payload_size_ = kPacketSize;
// Read the TS header: 4 bytes.
int syncword;
int transport_error_indicator;
int payload_unit_start_indicator;
int transport_priority;
int transport_scrambling_control;
int adaptation_field_control;
RCHECK(bit_reader.ReadBits(8, &syncword));
RCHECK(bit_reader.ReadBits(1, &transport_error_indicator));
RCHECK(bit_reader.ReadBits(1, &payload_unit_start_indicator));
RCHECK(bit_reader.ReadBits(1, &transport_priority));
RCHECK(bit_reader.ReadBits(13, &pid_));
RCHECK(bit_reader.ReadBits(2, &transport_scrambling_control));
RCHECK(bit_reader.ReadBits(2, &adaptation_field_control));
RCHECK(bit_reader.ReadBits(4, &continuity_counter_));
payload_unit_start_indicator_ = (payload_unit_start_indicator != 0);
payload_ += 4;
payload_size_ -= 4;
// Default values when no adaptation field.
discontinuity_indicator_ = false;
random_access_indicator_ = false;
// Done since no adaptation field.
if ((adaptation_field_control & 0x2) == 0)
return true;
// Read the adaptation field if needed.
int adaptation_field_length;
RCHECK(bit_reader.ReadBits(8, &adaptation_field_length));
DVLOG(LOG_LEVEL_TS) << "adaptation_field_length=" << adaptation_field_length;
payload_ += 1;
payload_size_ -= 1;
if ((adaptation_field_control & 0x1) == 0 &&
adaptation_field_length != 183) {
DVLOG(1) << "adaptation_field_length=" << adaptation_field_length;
return false;
}
if ((adaptation_field_control & 0x1) == 1 &&
adaptation_field_length > 182) {
DVLOG(1) << "adaptation_field_length=" << adaptation_field_length;
// This is not allowed by the spec.
// However, some badly encoded streams are using
// adaptation_field_length = 183
return false;
}
// adaptation_field_length = '0' is used to insert a single stuffing byte
// in the adaptation field of a transport stream packet.
if (adaptation_field_length == 0)
return true;
bool status = ParseAdaptationField(&bit_reader, adaptation_field_length);
payload_ += adaptation_field_length;
payload_size_ -= adaptation_field_length;
return status;
}
bool TsPacket::ParseAdaptationField(BitReader* bit_reader,
int adaptation_field_length) {
DCHECK_GT(adaptation_field_length, 0);
int adaptation_field_start_marker = bit_reader->bits_available() / 8;
int discontinuity_indicator;
int random_access_indicator;
int elementary_stream_priority_indicator;
int pcr_flag;
int opcr_flag;
int splicing_point_flag;
int transport_private_data_flag;
int adaptation_field_extension_flag;
RCHECK(bit_reader->ReadBits(1, &discontinuity_indicator));
RCHECK(bit_reader->ReadBits(1, &random_access_indicator));
RCHECK(bit_reader->ReadBits(1, &elementary_stream_priority_indicator));
RCHECK(bit_reader->ReadBits(1, &pcr_flag));
RCHECK(bit_reader->ReadBits(1, &opcr_flag));
RCHECK(bit_reader->ReadBits(1, &splicing_point_flag));
RCHECK(bit_reader->ReadBits(1, &transport_private_data_flag));
RCHECK(bit_reader->ReadBits(1, &adaptation_field_extension_flag));
discontinuity_indicator_ = (discontinuity_indicator != 0);
random_access_indicator_ = (random_access_indicator != 0);
if (pcr_flag) {
int64 program_clock_reference_base;
int reserved;
int program_clock_reference_extension;
RCHECK(bit_reader->ReadBits(33, &program_clock_reference_base));
RCHECK(bit_reader->ReadBits(6, &reserved));
RCHECK(bit_reader->ReadBits(9, &program_clock_reference_extension));
}
if (opcr_flag) {
int64 original_program_clock_reference_base;
int reserved;
int original_program_clock_reference_extension;
RCHECK(bit_reader->ReadBits(33, &original_program_clock_reference_base));
RCHECK(bit_reader->ReadBits(6, &reserved));
RCHECK(
bit_reader->ReadBits(9, &original_program_clock_reference_extension));
}
if (splicing_point_flag) {
int splice_countdown;
RCHECK(bit_reader->ReadBits(8, &splice_countdown));
}
if (transport_private_data_flag) {
int transport_private_data_length;
RCHECK(bit_reader->ReadBits(8, &transport_private_data_length));
RCHECK(bit_reader->SkipBits(8 * transport_private_data_length));
}
if (adaptation_field_extension_flag) {
int adaptation_field_extension_length;
RCHECK(bit_reader->ReadBits(8, &adaptation_field_extension_length));
RCHECK(bit_reader->SkipBits(8 * adaptation_field_extension_length));
}
// The rest of the adaptation field should be stuffing bytes.
int adaptation_field_remaining_size = adaptation_field_length -
(adaptation_field_start_marker - bit_reader->bits_available() / 8);
RCHECK(adaptation_field_remaining_size >= 0);
for (int k = 0; k < adaptation_field_remaining_size; k++) {
int stuffing_byte;
RCHECK(bit_reader->ReadBits(8, &stuffing_byte));
RCHECK(stuffing_byte == 0xff);
}
DVLOG(LOG_LEVEL_TS) << "random_access_indicator=" << random_access_indicator_;
return true;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,73 @@
// 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_TS_PACKET_H_
#define MEDIA_FORMATS_MP2T_TS_PACKET_H_
#include "base/basictypes.h"
namespace media {
class BitReader;
namespace mp2t {
class TsPacket {
public:
static const int kPacketSize = 188;
// Return the number of bytes to discard
// to be synchronized on a TS syncword.
static int Sync(const uint8* buf, int size);
// Parse a TS packet.
// Return a TsPacket only when parsing was successful.
// Return NULL otherwise.
static TsPacket* Parse(const uint8* buf, int size);
~TsPacket();
// TS header accessors.
bool payload_unit_start_indicator() const {
return payload_unit_start_indicator_;
}
int pid() const { return pid_; }
int continuity_counter() const { return continuity_counter_; }
bool discontinuity_indicator() const { return discontinuity_indicator_; }
bool random_access_indicator() const { return random_access_indicator_; }
// Return the offset and the size of the payload.
const uint8* payload() const { return payload_; }
int payload_size() const { return payload_size_; }
private:
TsPacket();
// Parse an Mpeg2 TS header.
// The buffer size should be at least |kPacketSize|
bool ParseHeader(const uint8* buf);
bool ParseAdaptationField(BitReader* bit_reader,
int adaptation_field_length);
// Size of the payload.
const uint8* payload_;
int payload_size_;
// TS header.
bool payload_unit_start_indicator_;
int pid_;
int continuity_counter_;
// Params from the adaptation field.
bool discontinuity_indicator_;
bool random_access_indicator_;
DISALLOW_COPY_AND_ASSIGN(TsPacket);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,40 @@
// 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_TS_SECTION_H_
#define MEDIA_FORMATS_MP2T_TS_SECTION_H_
namespace media {
namespace mp2t {
class TsSection {
public:
// From ISO/IEC 13818-1 or ITU H.222 spec: Table 2-3 - PID table.
enum SpecialPid {
kPidPat = 0x0,
kPidCat = 0x1,
kPidTsdt = 0x2,
kPidNullPacket = 0x1fff,
kPidMax = 0x1fff,
};
virtual ~TsSection() {}
// Parse the data bytes of the TS packet.
// Return true if parsing is successful.
virtual bool Parse(bool payload_unit_start_indicator,
const uint8* buf, int size) = 0;
// Process bytes that have not been processed yet (pending buffers in the
// pipe). Flush might thus results in frame emission, as an example.
virtual void Flush() = 0;
// Reset the state of the parser to its initial state.
virtual void Reset() = 0;
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,122 @@
// 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 "media/formats/mp2t/ts_section_pat.h"
#include <vector>
#include "base/logging.h"
#include "media/base/bit_reader.h"
#include "media/formats/mp2t/mp2t_common.h"
namespace media {
namespace mp2t {
TsSectionPat::TsSectionPat(const RegisterPmtCb& register_pmt_cb)
: register_pmt_cb_(register_pmt_cb),
version_number_(-1) {
}
TsSectionPat::~TsSectionPat() {
}
bool TsSectionPat::ParsePsiSection(BitReader* bit_reader) {
// Read the fixed section length.
int table_id;
int section_syntax_indicator;
int dummy_zero;
int reserved;
int section_length;
int transport_stream_id;
int version_number;
int current_next_indicator;
int section_number;
int last_section_number;
RCHECK(bit_reader->ReadBits(8, &table_id));
RCHECK(bit_reader->ReadBits(1, &section_syntax_indicator));
RCHECK(bit_reader->ReadBits(1, &dummy_zero));
RCHECK(bit_reader->ReadBits(2, &reserved));
RCHECK(bit_reader->ReadBits(12, &section_length));
RCHECK(section_length >= 5);
RCHECK(section_length <= 1021);
RCHECK(bit_reader->ReadBits(16, &transport_stream_id));
RCHECK(bit_reader->ReadBits(2, &reserved));
RCHECK(bit_reader->ReadBits(5, &version_number));
RCHECK(bit_reader->ReadBits(1, &current_next_indicator));
RCHECK(bit_reader->ReadBits(8, &section_number));
RCHECK(bit_reader->ReadBits(8, &last_section_number));
section_length -= 5;
// Perform a few verifications:
// - Table ID should be 0 for a PAT.
// - section_syntax_indicator should be one.
// - section length should not exceed 1021
RCHECK(table_id == 0x0);
RCHECK(section_syntax_indicator);
RCHECK(!dummy_zero);
// Both the program table and the CRC have a size multiple of 4.
// Note for pmt_pid_count: minus 4 to account for the CRC.
RCHECK((section_length % 4) == 0);
int pmt_pid_count = (section_length - 4) / 4;
// Read the variable length section: program table & crc.
std::vector<int> program_number_array(pmt_pid_count);
std::vector<int> pmt_pid_array(pmt_pid_count);
for (int k = 0; k < pmt_pid_count; k++) {
int reserved;
RCHECK(bit_reader->ReadBits(16, &program_number_array[k]));
RCHECK(bit_reader->ReadBits(3, &reserved));
RCHECK(bit_reader->ReadBits(13, &pmt_pid_array[k]));
}
int crc32;
RCHECK(bit_reader->ReadBits(32, &crc32));
// Just ignore the PAT if not applicable yet.
if (!current_next_indicator) {
DVLOG(1) << "Not supported: received a PAT not applicable yet";
return true;
}
// Ignore the program table if it hasn't changed.
if (version_number == version_number_)
return true;
// Both the MSE and the HLS spec specifies that TS streams should convey
// exactly one program.
if (pmt_pid_count > 1) {
DVLOG(1) << "Multiple programs detected in the Mpeg2 TS stream";
return false;
}
// Can now register the PMT.
#if !defined(NDEBUG)
int expected_version_number = version_number;
if (version_number_ >= 0)
expected_version_number = (version_number_ + 1) % 32;
DVLOG_IF(1, version_number != expected_version_number)
<< "Unexpected version number: "
<< version_number << " vs " << version_number_;
#endif
for (int k = 0; k < pmt_pid_count; k++) {
if (program_number_array[k] != 0) {
// Program numbers different from 0 correspond to PMT.
register_pmt_cb_.Run(program_number_array[k], pmt_pid_array[k]);
// Even if there are multiple programs, only one can be supported now.
// HLS: "Transport Stream segments MUST contain a single MPEG-2 Program."
break;
}
}
version_number_ = version_number;
return true;
}
void TsSectionPat::ResetPsiSection() {
version_number_ = -1;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,40 @@
// 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_TS_SECTION_PAT_H_
#define MEDIA_FORMATS_MP2T_TS_SECTION_PAT_H_
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "media/formats/mp2t/ts_section_psi.h"
namespace media {
namespace mp2t {
class TsSectionPat : public TsSectionPsi {
public:
// RegisterPmtCb::Run(int program_number, int pmt_pid);
typedef base::Callback<void(int, int)> RegisterPmtCb;
explicit TsSectionPat(const RegisterPmtCb& register_pmt_cb);
virtual ~TsSectionPat();
// TsSectionPsi implementation.
virtual bool ParsePsiSection(BitReader* bit_reader) OVERRIDE;
virtual void ResetPsiSection() OVERRIDE;
private:
RegisterPmtCb register_pmt_cb_;
// Parameters from the PAT.
int version_number_;
DISALLOW_COPY_AND_ASSIGN(TsSectionPat);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,312 @@
// 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 "media/formats/mp2t/ts_section_pes.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/bit_reader.h"
#include "media/base/buffers.h"
#include "media/formats/mp2t/es_parser.h"
#include "media/formats/mp2t/mp2t_common.h"
static const int kPesStartCode = 0x000001;
// Given that |time| is coded using 33 bits,
// UnrollTimestamp returns the corresponding unrolled timestamp.
// The unrolled timestamp is defined by:
// |time| + k * (2 ^ 33)
// where k is estimated so that the unrolled timestamp
// is as close as possible to |previous_unrolled_time|.
static int64 UnrollTimestamp(int64 previous_unrolled_time, int64 time) {
// Mpeg2 TS timestamps have an accuracy of 33 bits.
const int nbits = 33;
// |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;
return unrolled_time;
}
static 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 set to one.
return ((timestamp_section & 0x1) != 0) &&
((timestamp_section & 0x10000) != 0) &&
((timestamp_section & 0x100000000) != 0);
}
static int64 ConvertTimestampSectionToTimestamp(int64 timestamp_section) {
return (((timestamp_section >> 33) & 0x7) << 30) |
(((timestamp_section >> 17) & 0x7fff) << 15) |
(((timestamp_section >> 1) & 0x7fff) << 0);
}
namespace media {
namespace mp2t {
TsSectionPes::TsSectionPes(scoped_ptr<EsParser> es_parser)
: es_parser_(es_parser.release()),
wait_for_pusi_(true),
previous_pts_valid_(false),
previous_pts_(0),
previous_dts_valid_(false),
previous_dts_(0) {
DCHECK(es_parser_);
}
TsSectionPes::~TsSectionPes() {
}
bool TsSectionPes::Parse(bool payload_unit_start_indicator,
const uint8* buf, int size) {
// Ignore partial PES.
if (wait_for_pusi_ && !payload_unit_start_indicator)
return true;
bool parse_result = true;
if (payload_unit_start_indicator) {
// Try emitting a packet since we might have a pending PES packet
// with an undefined size.
// In this case, a unit is emitted when the next unit is coming.
int raw_pes_size;
const uint8* raw_pes;
pes_byte_queue_.Peek(&raw_pes, &raw_pes_size);
if (raw_pes_size > 0)
parse_result = Emit(true);
// Reset the state.
ResetPesState();
// Update the state.
wait_for_pusi_ = false;
}
// Add the data to the parser state.
if (size > 0)
pes_byte_queue_.Push(buf, size);
// Try emitting the current PES packet.
return (parse_result && Emit(false));
}
void TsSectionPes::Flush() {
// Try emitting a packet since we might have a pending PES packet
// with an undefined size.
Emit(true);
// Flush the underlying ES parser.
es_parser_->Flush();
}
void TsSectionPes::Reset() {
ResetPesState();
previous_pts_valid_ = false;
previous_pts_ = 0;
previous_dts_valid_ = false;
previous_dts_ = 0;
es_parser_->Reset();
}
bool TsSectionPes::Emit(bool emit_for_unknown_size) {
int raw_pes_size;
const uint8* raw_pes;
pes_byte_queue_.Peek(&raw_pes, &raw_pes_size);
// A PES should be at least 6 bytes.
// Wait for more data to come if not enough bytes.
if (raw_pes_size < 6)
return true;
// Check whether we have enough data to start parsing.
int pes_packet_length =
(static_cast<int>(raw_pes[4]) << 8) |
(static_cast<int>(raw_pes[5]));
if ((pes_packet_length == 0 && !emit_for_unknown_size) ||
(pes_packet_length != 0 && raw_pes_size < pes_packet_length + 6)) {
// Wait for more data to come either because:
// - there are not enough bytes,
// - or the PES size is unknown and the "force emit" flag is not set.
// (PES size might be unknown for video PES packet).
return true;
}
DVLOG(LOG_LEVEL_PES) << "pes_packet_length=" << pes_packet_length;
// Parse the packet.
bool parse_result = ParseInternal(raw_pes, raw_pes_size);
// Reset the state.
ResetPesState();
return parse_result;
}
bool TsSectionPes::ParseInternal(const uint8* raw_pes, int raw_pes_size) {
BitReader bit_reader(raw_pes, raw_pes_size);
// Read up to the pes_packet_length (6 bytes).
int packet_start_code_prefix;
int stream_id;
int pes_packet_length;
RCHECK(bit_reader.ReadBits(24, &packet_start_code_prefix));
RCHECK(bit_reader.ReadBits(8, &stream_id));
RCHECK(bit_reader.ReadBits(16, &pes_packet_length));
RCHECK(packet_start_code_prefix == kPesStartCode);
DVLOG(LOG_LEVEL_PES) << "stream_id=" << std::hex << stream_id << std::dec;
if (pes_packet_length == 0)
pes_packet_length = bit_reader.bits_available() / 8;
// Ignore the PES for unknown stream IDs.
// See ITU H.222 Table 2-22 "Stream_id assignments"
bool is_audio_stream_id = ((stream_id & 0xe0) == 0xc0);
bool is_video_stream_id = ((stream_id & 0xf0) == 0xe0);
if (!is_audio_stream_id && !is_video_stream_id)
return true;
// Read up to "pes_header_data_length".
int dummy_2;
int PES_scrambling_control;
int PES_priority;
int data_alignment_indicator;
int copyright;
int original_or_copy;
int pts_dts_flags;
int escr_flag;
int es_rate_flag;
int dsm_trick_mode_flag;
int additional_copy_info_flag;
int pes_crc_flag;
int pes_extension_flag;
int pes_header_data_length;
RCHECK(bit_reader.ReadBits(2, &dummy_2));
RCHECK(dummy_2 == 0x2);
RCHECK(bit_reader.ReadBits(2, &PES_scrambling_control));
RCHECK(bit_reader.ReadBits(1, &PES_priority));
RCHECK(bit_reader.ReadBits(1, &data_alignment_indicator));
RCHECK(bit_reader.ReadBits(1, &copyright));
RCHECK(bit_reader.ReadBits(1, &original_or_copy));
RCHECK(bit_reader.ReadBits(2, &pts_dts_flags));
RCHECK(bit_reader.ReadBits(1, &escr_flag));
RCHECK(bit_reader.ReadBits(1, &es_rate_flag));
RCHECK(bit_reader.ReadBits(1, &dsm_trick_mode_flag));
RCHECK(bit_reader.ReadBits(1, &additional_copy_info_flag));
RCHECK(bit_reader.ReadBits(1, &pes_crc_flag));
RCHECK(bit_reader.ReadBits(1, &pes_extension_flag));
RCHECK(bit_reader.ReadBits(8, &pes_header_data_length));
int pes_header_start_size = bit_reader.bits_available() / 8;
// Compute the size and the offset of the ES payload.
// "6" for the 6 bytes read before and including |pes_packet_length|.
// "3" for the 3 bytes read before and including |pes_header_data_length|.
int es_size = pes_packet_length - 3 - pes_header_data_length;
int es_offset = 6 + 3 + pes_header_data_length;
RCHECK(es_size >= 0);
RCHECK(es_offset + es_size <= raw_pes_size);
// Read the timing information section.
bool is_pts_valid = false;
bool is_dts_valid = false;
int64 pts_section = 0;
int64 dts_section = 0;
if (pts_dts_flags == 0x2) {
RCHECK(bit_reader.ReadBits(40, &pts_section));
RCHECK((((pts_section >> 36) & 0xf) == 0x2) &&
IsTimestampSectionValid(pts_section));
is_pts_valid = true;
}
if (pts_dts_flags == 0x3) {
RCHECK(bit_reader.ReadBits(40, &pts_section));
RCHECK(bit_reader.ReadBits(40, &dts_section));
RCHECK((((pts_section >> 36) & 0xf) == 0x3) &&
IsTimestampSectionValid(pts_section));
RCHECK((((dts_section >> 36) & 0xf) == 0x1) &&
IsTimestampSectionValid(dts_section));
is_pts_valid = true;
is_dts_valid = true;
}
// Convert and unroll the timestamps.
base::TimeDelta media_pts(kNoTimestamp());
base::TimeDelta media_dts(kNoTimestamp());
if (is_pts_valid) {
int64 pts = ConvertTimestampSectionToTimestamp(pts_section);
if (previous_pts_valid_)
pts = UnrollTimestamp(previous_pts_, pts);
previous_pts_ = pts;
previous_pts_valid_ = true;
media_pts = base::TimeDelta::FromMicroseconds((1000 * pts) / 90);
}
if (is_dts_valid) {
int64 dts = ConvertTimestampSectionToTimestamp(dts_section);
if (previous_dts_valid_)
dts = UnrollTimestamp(previous_dts_, dts);
previous_dts_ = dts;
previous_dts_valid_ = true;
media_dts = base::TimeDelta::FromMicroseconds((1000 * dts) / 90);
}
// Discard the rest of the PES packet header.
// TODO(damienv): check if some info of the PES packet header are useful.
DCHECK_EQ(bit_reader.bits_available() % 8, 0);
int pes_header_remaining_size = pes_header_data_length -
(pes_header_start_size - bit_reader.bits_available() / 8);
RCHECK(pes_header_remaining_size >= 0);
// Read the PES packet.
DVLOG(LOG_LEVEL_PES)
<< "Emit a reassembled PES:"
<< " size=" << es_size
<< " pts=" << media_pts.InMilliseconds()
<< " dts=" << media_dts.InMilliseconds()
<< " data_alignment_indicator=" << data_alignment_indicator;
return es_parser_->Parse(&raw_pes[es_offset], es_size, media_pts, media_dts);
}
void TsSectionPes::ResetPesState() {
pes_byte_queue_.Reset();
wait_for_pusi_ = true;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,64 @@
// 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_TS_SECTION_PES_H_
#define MEDIA_FORMATS_MP2T_TS_SECTION_PES_H_
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/byte_queue.h"
#include "media/formats/mp2t/ts_section.h"
namespace media {
namespace mp2t {
class EsParser;
class TsSectionPes : public TsSection {
public:
explicit TsSectionPes(scoped_ptr<EsParser> es_parser);
virtual ~TsSectionPes();
// TsSection implementation.
virtual bool Parse(bool payload_unit_start_indicator,
const uint8* buf, int size) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual void Reset() OVERRIDE;
private:
// Emit a reassembled PES packet.
// Return true if successful.
// |emit_for_unknown_size| is used to force emission for PES packets
// whose size is unknown.
bool Emit(bool emit_for_unknown_size);
// Parse a PES packet, return true if successful.
bool ParseInternal(const uint8* raw_pes, int raw_pes_size);
void ResetPesState();
// Bytes of the current PES.
ByteQueue pes_byte_queue_;
// ES parser.
scoped_ptr<EsParser> es_parser_;
// Do not start parsing before getting a unit start indicator.
bool wait_for_pusi_;
// Used to unroll PTS and DTS.
bool previous_pts_valid_;
int64 previous_pts_;
bool previous_dts_valid_;
int64 previous_dts_;
DISALLOW_COPY_AND_ASSIGN(TsSectionPes);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,122 @@
// 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 "media/formats/mp2t/ts_section_pmt.h"
#include <map>
#include "base/logging.h"
#include "media/base/bit_reader.h"
#include "media/formats/mp2t/mp2t_common.h"
namespace media {
namespace mp2t {
TsSectionPmt::TsSectionPmt(const RegisterPesCb& register_pes_cb)
: register_pes_cb_(register_pes_cb) {
}
TsSectionPmt::~TsSectionPmt() {
}
bool TsSectionPmt::ParsePsiSection(BitReader* bit_reader) {
// Read up to |last_section_number|.
int table_id;
int section_syntax_indicator;
int dummy_zero;
int reserved;
int section_length;
int program_number;
int version_number;
int current_next_indicator;
int section_number;
int last_section_number;
RCHECK(bit_reader->ReadBits(8, &table_id));
RCHECK(bit_reader->ReadBits(1, &section_syntax_indicator));
RCHECK(bit_reader->ReadBits(1, &dummy_zero));
RCHECK(bit_reader->ReadBits(2, &reserved));
RCHECK(bit_reader->ReadBits(12, &section_length));
int section_start_marker = bit_reader->bits_available() / 8;
RCHECK(bit_reader->ReadBits(16, &program_number));
RCHECK(bit_reader->ReadBits(2, &reserved));
RCHECK(bit_reader->ReadBits(5, &version_number));
RCHECK(bit_reader->ReadBits(1, &current_next_indicator));
RCHECK(bit_reader->ReadBits(8, &section_number));
RCHECK(bit_reader->ReadBits(8, &last_section_number));
// Perform a few verifications:
// - table ID should be 2 for a PMT.
// - section_syntax_indicator should be one.
// - section length should not exceed 1021.
RCHECK(table_id == 0x2);
RCHECK(section_syntax_indicator);
RCHECK(!dummy_zero);
RCHECK(section_length <= 1021);
RCHECK(section_number == 0);
RCHECK(last_section_number == 0);
// TODO(damienv):
// Verify that there is no mismatch between the program number
// and the program number that was provided in a PAT for the current PMT.
// Read the end of the fixed length section.
int pcr_pid;
int program_info_length;
RCHECK(bit_reader->ReadBits(3, &reserved));
RCHECK(bit_reader->ReadBits(13, &pcr_pid));
RCHECK(bit_reader->ReadBits(4, &reserved));
RCHECK(bit_reader->ReadBits(12, &program_info_length));
RCHECK(program_info_length < 1024);
// Read the program info descriptor.
// TODO(damienv): check wether any of the descriptors could be useful.
// Defined in section 2.6 of ISO-13818.
RCHECK(bit_reader->SkipBits(8 * program_info_length));
// Read the ES description table.
// The end of the PID map if 4 bytes away from the end of the section
// (4 bytes = size of the CRC).
int pid_map_end_marker = section_start_marker - section_length + 4;
std::map<int, int> pid_map;
while (bit_reader->bits_available() > 8 * pid_map_end_marker) {
int stream_type;
int reserved;
int pid_es;
int es_info_length;
RCHECK(bit_reader->ReadBits(8, &stream_type));
RCHECK(bit_reader->ReadBits(3, &reserved));
RCHECK(bit_reader->ReadBits(13, &pid_es));
RCHECK(bit_reader->ReadBits(4, &reserved));
RCHECK(bit_reader->ReadBits(12, &es_info_length));
// Do not register the PID right away.
// Wait for the end of the section to be fully parsed
// to make sure there is no error.
pid_map.insert(std::pair<int, int>(pid_es, stream_type));
// Read the ES info descriptors.
// TODO(damienv): check wether any of the descriptors could be useful.
// Defined in section 2.6 of ISO-13818.
RCHECK(bit_reader->SkipBits(8 * es_info_length));
}
// Read the CRC.
int crc32;
RCHECK(bit_reader->ReadBits(32, &crc32));
// Once the PMT has been proved to be correct, register the PIDs.
for (std::map<int, int>::iterator it = pid_map.begin();
it != pid_map.end(); ++it)
register_pes_cb_.Run(it->first, it->second);
return true;
}
void TsSectionPmt::ResetPsiSection() {
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,40 @@
// 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_TS_SECTION_PMT_H_
#define MEDIA_FORMATS_MP2T_TS_SECTION_PMT_H_
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "media/formats/mp2t/ts_section_psi.h"
namespace media {
namespace mp2t {
class TsSectionPmt : public TsSectionPsi {
public:
// RegisterPesCb::Run(int pes_pid, int stream_type);
// Stream type is defined in
// "Table 2-34 Stream type assignments" in H.222
// TODO(damienv): add the program number.
typedef base::Callback<void(int, int)> RegisterPesCb;
explicit TsSectionPmt(const RegisterPesCb& register_pes_cb);
virtual ~TsSectionPmt();
// Mpeg2TsPsiParser implementation.
virtual bool ParsePsiSection(BitReader* bit_reader) OVERRIDE;
virtual void ResetPsiSection() OVERRIDE;
private:
RegisterPesCb register_pes_cb_;
DISALLOW_COPY_AND_ASSIGN(TsSectionPmt);
};
} // namespace mp2t
} // namespace media
#endif

View File

@ -0,0 +1,132 @@
// 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 "media/formats/mp2t/ts_section_psi.h"
#include "base/basictypes.h"
#include "base/logging.h"
#include "media/base/bit_reader.h"
#include "media/formats/mp2t/mp2t_common.h"
static bool IsCrcValid(const uint8* buf, int size) {
uint32 crc = 0xffffffffu;
const uint32 kCrcPoly = 0x4c11db7;
for (int k = 0; k < size; k++) {
int nbits = 8;
uint32 data_msb_aligned = buf[k];
data_msb_aligned <<= (32 - nbits);
while (nbits > 0) {
if ((data_msb_aligned ^ crc) & 0x80000000) {
crc <<= 1;
crc ^= kCrcPoly;
} else {
crc <<= 1;
}
data_msb_aligned <<= 1;
nbits--;
}
}
return (crc == 0);
}
namespace media {
namespace mp2t {
TsSectionPsi::TsSectionPsi()
: wait_for_pusi_(true),
leading_bytes_to_discard_(0) {
}
TsSectionPsi::~TsSectionPsi() {
}
bool TsSectionPsi::Parse(bool payload_unit_start_indicator,
const uint8* buf, int size) {
// Ignore partial PSI.
if (wait_for_pusi_ && !payload_unit_start_indicator)
return true;
if (payload_unit_start_indicator) {
// Reset the state of the PSI section.
ResetPsiState();
// Update the state.
wait_for_pusi_ = false;
DCHECK_GE(size, 1);
int pointer_field = buf[0];
leading_bytes_to_discard_ = pointer_field;
buf++;
size--;
}
// Discard some leading bytes if needed.
if (leading_bytes_to_discard_ > 0) {
int nbytes_to_discard = std::min(leading_bytes_to_discard_, size);
buf += nbytes_to_discard;
size -= nbytes_to_discard;
leading_bytes_to_discard_ -= nbytes_to_discard;
}
if (size == 0)
return true;
// Add the data to the parser state.
psi_byte_queue_.Push(buf, size);
int raw_psi_size;
const uint8* raw_psi;
psi_byte_queue_.Peek(&raw_psi, &raw_psi_size);
// Check whether we have enough data to start parsing.
if (raw_psi_size < 3)
return true;
int section_length =
((static_cast<int>(raw_psi[1]) << 8) |
(static_cast<int>(raw_psi[2]))) & 0xfff;
if (section_length >= 1021)
return false;
int psi_length = section_length + 3;
if (raw_psi_size < psi_length) {
// Don't throw an error when there is not enough data,
// just wait for more data to come.
return true;
}
// There should not be any trailing bytes after a PMT.
// Instead, the pointer field should be used to stuff bytes.
DVLOG_IF(1, raw_psi_size > psi_length)
<< "Trailing bytes after a PSI section: "
<< psi_length << " vs " << raw_psi_size;
// Verify the CRC.
RCHECK(IsCrcValid(raw_psi, psi_length));
// Parse the PSI section.
BitReader bit_reader(raw_psi, raw_psi_size);
bool status = ParsePsiSection(&bit_reader);
if (status)
ResetPsiState();
return status;
}
void TsSectionPsi::Flush() {
}
void TsSectionPsi::Reset() {
ResetPsiSection();
ResetPsiState();
}
void TsSectionPsi::ResetPsiState() {
wait_for_pusi_ = true;
psi_byte_queue_.Reset();
leading_bytes_to_discard_ = 0;
}
} // namespace mp2t
} // namespace media

View File

@ -0,0 +1,54 @@
// 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_TS_SECTION_PSI_H_
#define MEDIA_FORMATS_MP2T_TS_SECTION_PSI_H_
#include "base/compiler_specific.h"
#include "media/base/byte_queue.h"
#include "media/formats/mp2t/ts_section.h"
namespace media {
class BitReader;
namespace mp2t {
class TsSectionPsi : public TsSection {
public:
TsSectionPsi();
virtual ~TsSectionPsi();
// TsSection implementation.
virtual bool Parse(bool payload_unit_start_indicator,
const uint8* buf, int size) OVERRIDE;
virtual void Flush() OVERRIDE;
virtual void Reset() OVERRIDE;
// Parse the content of the PSI section.
virtual bool ParsePsiSection(BitReader* bit_reader) = 0;
// Reset the state of the PSI section.
virtual void ResetPsiSection() = 0;
private:
void ResetPsiState();
// Bytes of the current PSI.
ByteQueue psi_byte_queue_;
// Do not start parsing before getting a unit start indicator.
bool wait_for_pusi_;
// Number of leading bytes to discard (pointer field).
int leading_bytes_to_discard_;
DISALLOW_COPY_AND_ASSIGN(TsSectionPsi);
};
} // namespace mp2t
} // namespace media
#endif