Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Parse one frame mpeg-ts video #1015

Merged
merged 11 commits into from
Oct 28, 2022
16 changes: 8 additions & 8 deletions packager/media/codecs/h264_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ bool H264SliceHeader::IsSISlice() const {
} \
} while (0)

#define READ_LONG_OR_RETURN(out) READ_BITS_OR_RETURN(32, out)

#define READ_BOOL_OR_RETURN(out) \
do { \
int _out; \
Expand Down Expand Up @@ -524,14 +526,12 @@ H264Parser::Result H264Parser::ParseVUIParameters(H26xBitReader* br,
READ_UE_OR_RETURN(&data); // chroma_sample_loc_type_bottom_field
}

// Read and ignore timing info.
READ_BOOL_OR_RETURN(&data); // timing_info_present_flag
if (data) {
READ_BITS_OR_RETURN(16, &data); // num_units_in_tick
READ_BITS_OR_RETURN(16, &data); // num_units_in_tick
READ_BITS_OR_RETURN(16, &data); // time_scale
READ_BITS_OR_RETURN(16, &data); // time_scale
READ_BOOL_OR_RETURN(&data); // fixed_frame_rate_flag
// Read timing info.
READ_BOOL_OR_RETURN(&sps->timing_info_present_flag);
if (sps->timing_info_present_flag) {
READ_LONG_OR_RETURN(&sps->num_units_in_tick);
READ_LONG_OR_RETURN(&sps->time_scale);
READ_BOOL_OR_RETURN(&sps->fixed_frame_rate_flag);
}

// Read and ignore NAL HRD parameters, if present.
Expand Down
5 changes: 5 additions & 0 deletions packager/media/codecs/h264_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ struct H264Sps {
int sar_height; // Set to 0 when not specified.
int transfer_characteristics;

bool timing_info_present_flag;
long num_units_in_tick;
long time_scale;
bool fixed_frame_rate_flag;

bool bitstream_restriction_flag;
int max_num_reorder_frames;
int max_dec_frame_buffering;
Expand Down
20 changes: 15 additions & 5 deletions packager/media/codecs/h265_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
return status; \
} while (false)

#define READ_BITS_OR_RETURN(num_bits, out) \
do { \
if (!br->ReadBits(num_bits, (out))) { \
DVLOG(1) \
<< "Error in stream: unexpected EOS while trying to read " #out; \
return kInvalidStream; \
} \
} while (0)

#define READ_LONG_OR_RETURN(out) READ_BITS_OR_RETURN(32, out)

namespace shaka {
namespace media {

Expand Down Expand Up @@ -688,11 +699,10 @@ H265Parser::Result H265Parser::ParseVuiParameters(int max_num_sub_layers_minus1,
TRUE_OR_RETURN(br->ReadUE(&ignored)); // def_disp_win_bottom_offset
}

bool vui_timing_info_present_flag;
TRUE_OR_RETURN(br->ReadBool(&vui_timing_info_present_flag));
if (vui_timing_info_present_flag) {
// vui_num_units_in_tick, vui_time_scale
TRUE_OR_RETURN(br->SkipBits(32 + 32));
TRUE_OR_RETURN(br->ReadBool(&vui->vui_timing_info_present_flag));
if (vui->vui_timing_info_present_flag) {
READ_LONG_OR_RETURN(&vui->vui_num_units_in_tick);
READ_LONG_OR_RETURN(&vui->vui_time_scale);

bool vui_poc_proportional_to_timing_flag;
TRUE_OR_RETURN(br->ReadBool(&vui_poc_proportional_to_timing_flag));
Expand Down
4 changes: 4 additions & 0 deletions packager/media/codecs/h265_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ struct H265VuiParameters {
int sar_height = 0;
int transfer_characteristics = 0;

bool vui_timing_info_present_flag = false;
long vui_num_units_in_tick = 0;
long vui_time_scale = 0;

bool bitstream_restriction_flag = false;
int min_spatial_segmentation_idc = 0;

Expand Down
16 changes: 16 additions & 0 deletions packager/media/formats/mp2t/es_parser_h264.cc
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@ bool EsParserH264::UpdateVideoDecoderConfig(int pps_id) {
return true;
}

int64_t EsParserH264::CalculateSampleDuration(int pps_id) {
auto pps = h264_parser_->GetPps(pps_id);
if (pps) {
auto sps_id = pps->seq_parameter_set_id;
auto sps = h264_parser_->GetSps(sps_id);
if (sps && sps->timing_info_present_flag && sps->fixed_frame_rate_flag) {
return static_cast<int64_t>(kMpeg2Timescale) * sps->num_units_in_tick *
2 / sps->time_scale;
}
}
LOG(WARNING) << "[MPEG-2 TS] PID " << pid()
<< " Cannot calculate frame rate from SPS.";
// Returns arbitrary safe duration
return 0.001 * kMpeg2Timescale; // 1ms.
}

} // namespace mp2t
} // namespace media
} // namespace shaka
3 changes: 2 additions & 1 deletion packager/media/formats/mp2t/es_parser_h264.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class EsParserH264 : public EsParserH26x {
// Update the video decoder config based on an H264 SPS.
// Return true if successful.
bool UpdateVideoDecoderConfig(int sps_id) override;

// Calculate video sample duration based on SPS data
int64_t CalculateSampleDuration(int pps_id) override;
// Callback to pass the stream configuration.
NewStreamInfoCB new_stream_info_cb_;

Expand Down
18 changes: 18 additions & 0 deletions packager/media/formats/mp2t/es_parser_h265.cc
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ bool EsParserH265::UpdateVideoDecoderConfig(int pps_id) {
return true;
}

int64_t EsParserH265::CalculateSampleDuration(int pps_id) {
auto pps = h265_parser_->GetPps(pps_id);
if (pps) {
auto sps_id = pps->seq_parameter_set_id;
auto sps = h265_parser_->GetSps(sps_id);
if (sps && sps->vui_parameters_present &&
sps->vui_parameters.vui_timing_info_present_flag) {
return static_cast<int64_t>(kMpeg2Timescale) *
sps->vui_parameters.vui_num_units_in_tick * 2 /
sps->vui_parameters.vui_time_scale;
}
}
LOG(WARNING) << "[MPEG-2 TS] PID " << pid()
<< " Cannot calculate frame rate from SPS.";
// Returns arbitrary safe duration
return 0.001 * kMpeg2Timescale; // 1ms.
}

} // namespace mp2t
} // namespace media
} // namespace shaka
1 change: 1 addition & 0 deletions packager/media/formats/mp2t/es_parser_h265.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class EsParserH265 : public EsParserH26x {
// Return true if successful.
bool UpdateVideoDecoderConfig(int sps_id) override;

int64_t CalculateSampleDuration(int pps_id) override;
// Callback to pass the stream configuration.
NewStreamInfoCB new_stream_info_cb_;

Expand Down
8 changes: 6 additions & 2 deletions packager/media/formats/mp2t/es_parser_h26x.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ bool EsParserH26x::Flush() {

if (pending_sample_) {
// Flush pending sample.
DCHECK(pending_sample_duration_);
if (!pending_sample_duration_) {
pending_sample_duration_ = CalculateSampleDuration(pending_sample_pps_id_);
}
pending_sample_->set_duration(pending_sample_duration_);
emit_sample_cb_.Run(std::move(pending_sample_));
}
Expand Down Expand Up @@ -330,7 +332,8 @@ bool EsParserH26x::EmitFrame(int64_t access_unit_pos,
pending_sample_->set_duration(sample_duration);

const int kArbitraryGapScale = 10;
if (sample_duration > kArbitraryGapScale * pending_sample_duration_) {
if (pending_sample_duration_ &&
sample_duration > kArbitraryGapScale * pending_sample_duration_) {
LOG(WARNING) << "[MPEG-2 TS] PID " << pid() << " Possible GAP at dts "
<< pending_sample_->dts() << " with next sample at dts "
<< media_sample->dts() << " (difference "
Expand All @@ -342,6 +345,7 @@ bool EsParserH26x::EmitFrame(int64_t access_unit_pos,
emit_sample_cb_.Run(std::move(pending_sample_));
}
pending_sample_ = media_sample;
pending_sample_pps_id_ = pps_id;

return true;
}
Expand Down
3 changes: 3 additions & 0 deletions packager/media/formats/mp2t/es_parser_h26x.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class EsParserH26x : public EsParser {
int access_unit_size,
bool is_key_frame,
int pps_id);
// Calculates frame duration based on SPS frame data
virtual int64_t CalculateSampleDuration(int pps_id) = 0;

// Callback to pass the frames.
EmitSampleCB emit_sample_cb_;
Expand Down Expand Up @@ -127,6 +129,7 @@ class EsParserH26x : public EsParser {

// Frame for which we do not yet have a duration.
std::shared_ptr<MediaSample> pending_sample_;
int pending_sample_pps_id_ = -1;
int64_t pending_sample_duration_ = 0;

// Indicates whether waiting for first key frame.
Expand Down
38 changes: 38 additions & 0 deletions packager/media/formats/mp2t/es_parser_h26x_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ class TestableEsParser : public EsParserH26x {
}

private:
int64_t CalculateSampleDuration(int pps_id) override {
// Typical 40ms - frame duration with 25 FPS
return 0.04 * 90000;
}

const int kTestPpsId = 123;

Nalu::CodecType codec_type_;
Expand Down Expand Up @@ -179,6 +184,7 @@ class EsParserH26xTest : public testing::Test {
const std::vector<uint8_t> sample_data(
sample->data(), sample->data() + sample->data_size());
EXPECT_EQ(samples_[sample_id], sample_data);
media_samples_.push_back(sample);
}

void NewVideoConfig(std::shared_ptr<StreamInfo> config) {
Expand All @@ -187,6 +193,7 @@ class EsParserH26xTest : public testing::Test {

protected:
std::vector<std::vector<uint8_t>> samples_;
std::vector<std::shared_ptr<MediaSample>> media_samples_;
size_t sample_count_;
bool has_stream_info_;
};
Expand Down Expand Up @@ -377,6 +384,10 @@ TEST_F(EsParserH26xTest, H264BasicSupport) {
RunTest(Nalu::kH264, kData, arraysize(kData));
EXPECT_EQ(3u, sample_count_);
EXPECT_TRUE(has_stream_info_);
EXPECT_EQ(3u, media_samples_.size());
for (size_t i = 0; i < media_samples_.size(); i++) {
EXPECT_GT(media_samples_[i]->duration(), 0u);
}
}

// This is not compliant to H264 spec, but VLC generates streams like this. See
Expand Down Expand Up @@ -453,6 +464,33 @@ TEST_F(EsParserH26xTest, H264DoesNotStartOnRsv) {
EXPECT_TRUE(has_stream_info_);
}

TEST_F(EsParserH26xTest, H264ContainsOnlyOneFrame) {
const H26xNaluType kData[] = {
kSeparator,
kH264Aud,
kH264Sps,
kH264VclKeyFrame,
};

RunTest(Nalu::kH264, kData, arraysize(kData));
EXPECT_TRUE(has_stream_info_);
EXPECT_EQ(1u, sample_count_);
EXPECT_EQ(1u, media_samples_.size());
EXPECT_GT(media_samples_[0]->duration(), 0u);
}

TEST_F(EsParserH26xTest, H265ContainsOnlyOneFrame) {
const H26xNaluType kData[] = {
kSeparator, kH265Aud, kH265Sps, kH265VclKeyFrame,
};

RunTest(Nalu::kH265, kData, arraysize(kData));
EXPECT_TRUE(has_stream_info_);
EXPECT_EQ(1u, sample_count_);
EXPECT_EQ(1u, media_samples_.size());
EXPECT_GT(media_samples_[0]->duration(), 0u);
}

} // namespace mp2t
} // namespace media
} // namespace shaka
11 changes: 6 additions & 5 deletions packager/media/formats/mp2t/mp2t_media_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ bool PidState::PushTsPacket(const TsPacket& ts_packet) {
// just discard the incoming TS packet.
if (!enable_)
return true;

// TODO(bzd): continuity_counter_ is never set
int expected_continuity_counter = (continuity_counter_ + 1) % 16;
if (continuity_counter_ >= 0 &&
ts_packet.continuity_counter() != expected_continuity_counter) {
Expand Down Expand Up @@ -218,10 +218,11 @@ bool Mp2tMediaParser::Parse(const uint8_t* buf, int size) {
ts_byte_queue_.Pop(1);
continue;
}
DVLOG(LOG_LEVEL_TS)
<< "Processing PID=" << ts_packet->pid()
<< " start_unit=" << ts_packet->payload_unit_start_indicator();

DVLOG(LOG_LEVEL_TS) << "Processing PID=" << ts_packet->pid()
<< " start_unit="
<< ts_packet->payload_unit_start_indicator()
<< " continuity_counter="
<< ts_packet->continuity_counter();
// Parse the section.
auto it = pids_.find(ts_packet->pid());
if (it == pids_.end() &&
Expand Down
12 changes: 5 additions & 7 deletions packager/media/formats/mp2t/ts_section_pes.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ bool TsSectionPes::ParseInternal(const uint8_t* raw_pes, int raw_pes_size) {
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;
DVLOG(LOG_LEVEL_PES) << "stream_id=" << stream_id;
if (pes_packet_length == 0)
pes_packet_length = static_cast<int>(bit_reader.bits_available()) / 8;

Expand Down Expand Up @@ -299,12 +299,10 @@ bool TsSectionPes::ParseInternal(const uint8_t* raw_pes, int raw_pes_size) {
RCHECK(pes_header_remaining_size >= 0);

// Read the PES packet.
DVLOG(LOG_LEVEL_PES)
<< "Emit a reassembled PES:"
<< " size=" << es_size
<< " pts=" << media_pts
<< " dts=" << media_dts
<< " data_alignment_indicator=" << data_alignment_indicator;
DVLOG(LOG_LEVEL_PES) << "Emit a reassembled PES:"
<< " size=" << es_size << " pts=" << media_pts
<< " dts=" << media_dts << " data_alignment_indicator="
<< data_alignment_indicator;
return es_parser_->Parse(&raw_pes[es_offset], es_size, media_pts, media_dts);
}

Expand Down