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

feat: Update continuity counters in MPEG-TS packaging #14

Merged
merged 20 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions packager/live_packager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@
#include <packager/file.h>
#include <packager/live_packager.h>
#include <packager/media/base/aes_decryptor.h>
#include <packager/media/base/byte_queue.h>
#include <packager/media/base/key_source.h>
#include <packager/media/base/media_sample.h>
#include <packager/media/base/raw_key_source.h>
#include <packager/media/base/stream_info.h>
#include <packager/media/formats/mp2t/mp2t_media_parser.h>
#include <packager/media/formats/mp2t/ts_packet.h>
#include <packager/media/formats/mp2t/ts_section.h>
#include <packager/media/formats/mp2t/ts_section_pat.h>
#include <packager/media/formats/mp2t/ts_section_pes.h>
#include <packager/media/formats/mp2t/ts_section_pmt.h>
#include <packager/media/formats/mp4/box_definitions.h>
#include <packager/media/formats/mp4/box_reader.h>
#include <packager/media/formats/mp4/mp4_media_parser.h>
Expand Down Expand Up @@ -427,6 +434,7 @@ TEST(GeneratePSSHData, FailsOnInvalidInput) {
class LivePackagerBaseTest : public ::testing::Test {
public:
void SetUp() override {
mp2t_parser_ = std::make_unique<media::mp2t::Mp2tMediaParser>();
key_.assign(kKey, kKey + std::size(kKey));
iv_.assign(kIv, kIv + std::size(kIv));
key_id_.assign(kKeyId, kKeyId + std::size(kKeyId));
Expand Down Expand Up @@ -457,6 +465,8 @@ class LivePackagerBaseTest : public ::testing::Test {
std::vector<uint8_t> key_;
std::vector<uint8_t> iv_;
std::vector<uint8_t> key_id_;

std::unique_ptr<media::mp2t::Mp2tMediaParser> mp2t_parser_;
};

TEST_F(LivePackagerBaseTest, InitSegmentOnly) {
Expand Down Expand Up @@ -597,6 +607,80 @@ TEST_F(LivePackagerBaseTest, EncryptionFailure) {
}
}

TEST_F(LivePackagerBaseTest, CheckContinutityCounter) {
std::vector<uint8_t> init_segment_buffer = ReadTestDataFile("input/init.mp4");
ASSERT_FALSE(init_segment_buffer.empty());

media::ByteQueue ts_byte_queue;

for (unsigned int i = 0; i < kNumSegments; i++) {
std::string segment_num = absl::StrFormat("input/%04d.m4s", i);
std::vector<uint8_t> segment_buffer = ReadTestDataFile(segment_num);
ASSERT_FALSE(segment_buffer.empty());

SegmentData init_seg(init_segment_buffer.data(),
init_segment_buffer.size());
SegmentData media_seg(segment_buffer.data(), segment_buffer.size());

FullSegmentBuffer out;

LiveConfig live_config;
live_config.format = LiveConfig::OutputFormat::TS;
live_config.track_type = LiveConfig::TrackType::VIDEO;
live_config.protection_scheme = LiveConfig::EncryptionScheme::NONE;
live_config.segment_number = i;

SetupLivePackagerConfig(live_config);
ASSERT_EQ(Status::OK, live_packager_->Package(init_seg, media_seg, out));
ASSERT_GT(out.SegmentSize(), 0);

ts_byte_queue.Push(out.SegmentData(), static_cast<int>(out.SegmentSize()));
while (true) {
const uint8_t* ts_buffer;
int ts_buffer_size;
ts_byte_queue.Peek(&ts_buffer, &ts_buffer_size);

if (ts_buffer_size < media::mp2t::TsPacket::kPacketSize)
break;

// Synchronization.
int skipped_bytes =
media::mp2t::TsPacket::Sync(ts_buffer, ts_buffer_size);
if (skipped_bytes > 0) {
LOG(WARNING) << "Packet not aligned on a TS syncword:"
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
<< " skipped_bytes=" << skipped_bytes;
ts_byte_queue.Pop(skipped_bytes);
continue;
}

// Parse the TS header, skipping 1 byte if the header is invalid.
std::unique_ptr<media::mp2t::TsPacket> ts_packet(
media::mp2t::TsPacket::Parse(ts_buffer, ts_buffer_size));
if (!ts_packet) {
LOG(WARNING) << "Error: invalid TS packet";
ts_byte_queue.Pop(1);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

if (ts_packet->payload_unit_start_indicator() &&
ts_packet->pid() == media::mp2t::TsSection::kPidPat) {
LOG(WARNING) << "Processing PID=" << ts_packet->pid() << " start_unit="
<< ts_packet->payload_unit_start_indicator()
<< " continuity_counter="
<< ts_packet->continuity_counter();

// check the PAT continuity counter is in sync with the segment number.
EXPECT_EQ(ts_packet->continuity_counter(), live_config.segment_number);
}

// Go to the next packet.
ts_byte_queue.Pop(media::mp2t::TsPacket::kPacketSize);
}

ts_byte_queue.Reset();
}
}

TEST_F(LivePackagerBaseTest, CustomMoofSequenceNumber) {
std::vector<uint8_t> init_segment_buffer = ReadTestDataFile("input/init.mp4");
ASSERT_FALSE(init_segment_buffer.empty());
Expand Down
21 changes: 15 additions & 6 deletions packager/media/formats/mp2t/continuity_counter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ namespace shaka {
namespace media {
namespace mp2t {

ContinuityCounter::ContinuityCounter() {}
ContinuityCounter::~ContinuityCounter() {}
ContinuityCounter::ContinuityCounter() : ContinuityCounter(0) {}
ContinuityCounter::ContinuityCounter(unsigned int segment_number)
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
: counter_(segment_number & 0xF) {}

int ContinuityCounter::GetNext() {
int ret = counter_;
++counter_;
counter_ %= 16;
ContinuityCounter::~ContinuityCounter() = default;

unsigned int ContinuityCounter::GetNext() {
// int ret = counter_;
// ++counter_;
// counter_ %= 16;
// return ret;
unsigned int ret = ((counter_++) & 0x0F);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
return ret;
}

unsigned int ContinuityCounter::GetContinuityCounter() const {
return counter_;
}

} // namespace mp2t
} // namespace media
} // namespace shaka
8 changes: 6 additions & 2 deletions packager/media/formats/mp2t/continuity_counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,20 @@ namespace mp2t {

class ContinuityCounter {
public:
ContinuityCounter(unsigned int segment_number);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
ContinuityCounter();
~ContinuityCounter();

/// As specified by the spec, this starts from 0 and is incremented by 1 until
/// it wraps back to 0 when it reaches 16.
/// @return counter value.
int GetNext();
unsigned int GetNext();
lfordyce marked this conversation as resolved.
Show resolved Hide resolved

/// @return the current value of the continuity counter.
[[nodiscard]] unsigned int GetContinuityCounter() const;
lfordyce marked this conversation as resolved.
Show resolved Hide resolved

private:
int counter_ = 0;
unsigned int counter_;
DISALLOW_COPY_AND_ASSIGN(ContinuityCounter);
};

Expand Down
30 changes: 24 additions & 6 deletions packager/media/formats/mp2t/program_map_table_writer.cc
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const int kVersion1 = 1;

// Values for current_next_indicator.
const int kCurrent = 1;
const int kNext= 0;
const int kNext = 0;

// Program number is 16 bits but 8 bits is sufficient.
const uint8_t kProgramNumber = 0x01;
Expand Down Expand Up @@ -172,8 +172,7 @@ void WritePmtWithParameters(uint8_t stream_type,
pmt_body.AppendInt(kProgramNumber);
// resevered bits then version and current_next_indicator.
pmt_body.AppendInt(
static_cast<uint8_t>(0xC0 |
static_cast<uint8_t>(version) << 1 |
static_cast<uint8_t>(0xC0 | static_cast<uint8_t>(version) << 1 |
static_cast<uint8_t>(current_next_indicator)));
// section number.
pmt_body.AppendInt(static_cast<uint8_t>(0x00));
Expand Down Expand Up @@ -215,7 +214,15 @@ void WritePmtWithParameters(uint8_t stream_type,

} // namespace

ProgramMapTableWriter::ProgramMapTableWriter(Codec codec) : codec_(codec) {}
ProgramMapTableWriter::ProgramMapTableWriter(Codec codec)
: ProgramMapTableWriter(codec, 0) {}

// use segment number as continuity counter as PMT types have single packets,
// therefore, using the segment number as the CC will be continuous across
// segments
ProgramMapTableWriter::ProgramMapTableWriter(Codec codec,
unsigned int segment_number)
: codec_(codec), continuity_counter_(ContinuityCounter(segment_number)) {}
lfordyce marked this conversation as resolved.
Show resolved Hide resolved

bool ProgramMapTableWriter::EncryptedSegmentPmt(BufferWriter* writer) {
if (encrypted_pmt_.Size() == 0) {
Expand Down Expand Up @@ -288,7 +295,12 @@ bool ProgramMapTableWriter::ClearSegmentPmt(BufferWriter* writer) {
}

VideoProgramMapTableWriter::VideoProgramMapTableWriter(Codec codec)
: ProgramMapTableWriter(codec) {}
: VideoProgramMapTableWriter(codec, 0) {}

VideoProgramMapTableWriter::VideoProgramMapTableWriter(
Codec codec,
unsigned int segment_number)
: ProgramMapTableWriter(codec, segment_number) {}

bool VideoProgramMapTableWriter::WriteDescriptors(
BufferWriter* descriptors) const {
Expand All @@ -308,7 +320,13 @@ bool VideoProgramMapTableWriter::WriteDescriptors(
AudioProgramMapTableWriter::AudioProgramMapTableWriter(
Codec codec,
const std::vector<uint8_t>& audio_specific_config)
: ProgramMapTableWriter(codec),
: AudioProgramMapTableWriter(codec, audio_specific_config, 0) {}

AudioProgramMapTableWriter::AudioProgramMapTableWriter(
Codec codec,
const std::vector<uint8_t>& audio_specific_config,
unsigned int segment_number)
: ProgramMapTableWriter(codec, segment_number),
audio_specific_config_(audio_specific_config) {
DCHECK(!audio_specific_config.empty());
}
Expand Down
7 changes: 6 additions & 1 deletion packager/media/formats/mp2t/program_map_table_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace mp2t {
/// Puts PMT into TS packets and writes them to buffer.
class ProgramMapTableWriter {
public:
explicit ProgramMapTableWriter(Codec codec);
ProgramMapTableWriter(Codec codec);
ProgramMapTableWriter(Codec codec, unsigned int segment_number);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
virtual ~ProgramMapTableWriter() = default;

/// Writes TS packets with PMT for encrypted segments.
Expand Down Expand Up @@ -64,6 +65,7 @@ class ProgramMapTableWriter {
class VideoProgramMapTableWriter : public ProgramMapTableWriter {
public:
explicit VideoProgramMapTableWriter(Codec codec);
VideoProgramMapTableWriter(Codec codec, unsigned int segment_number);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
~VideoProgramMapTableWriter() override = default;

private:
Expand All @@ -79,6 +81,9 @@ class AudioProgramMapTableWriter : public ProgramMapTableWriter {
public:
AudioProgramMapTableWriter(Codec codec,
const std::vector<uint8_t>& audio_specific_config);
AudioProgramMapTableWriter(Codec codec,
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
const std::vector<uint8_t>& audio_specific_config,
unsigned int segment_number);
~AudioProgramMapTableWriter() override = default;

private:
Expand Down
27 changes: 14 additions & 13 deletions packager/media/formats/mp2t/ts_segmenter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Status TsSegmenter::Finalize() {

Status TsSegmenter::AddSample(const MediaSample& sample) {
if (!ts_writer_) {
uint32_t segment_number = muxer_options_.mp4_params.sequence_number;
std::unique_ptr<ProgramMapTableWriter> pmt_writer;
if (codec_ == kCodecAC3) {
// https://goo.gl/N7Tvqi MPEG-2 Stream Encryption Format for HTTP Live
Expand All @@ -91,15 +92,16 @@ Status TsSegmenter::AddSample(const MediaSample& sample) {
}
const std::vector<uint8_t> setup_data(sample.data(),
sample.data() + kSetupDataSize);
pmt_writer.reset(new AudioProgramMapTableWriter(codec_, setup_data));
} else if (IsAudioCodec(codec_)) {
pmt_writer.reset(
new AudioProgramMapTableWriter(codec_, audio_codec_config_));
new AudioProgramMapTableWriter(codec_, setup_data, segment_number));
} else if (IsAudioCodec(codec_)) {
pmt_writer.reset(new AudioProgramMapTableWriter(
codec_, audio_codec_config_, segment_number));
} else {
DCHECK(IsVideoCodec(codec_));
pmt_writer.reset(new VideoProgramMapTableWriter(codec_));
pmt_writer.reset(new VideoProgramMapTableWriter(codec_, segment_number));
}
ts_writer_.reset(new TsWriter(std::move(pmt_writer)));
ts_writer_.reset(new TsWriter(std::move(pmt_writer), segment_number));
}

if (sample.is_encrypted())
Expand Down Expand Up @@ -148,7 +150,6 @@ Status TsSegmenter::WritePesPackets() {
return status;

if (listener_ && IsVideoCodec(codec_) && pes_packet->is_key_frame()) {

uint64_t start_pos = segment_buffer_.Size();
const int64_t timestamp = pes_packet->pts();
if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_))
Expand Down Expand Up @@ -178,8 +179,8 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) {
if (!segment_started_)
return Status::OK;
std::string segment_path =
GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
segment_number_++, muxer_options_.bandwidth);
GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
segment_number_++, muxer_options_.bandwidth);

const int64_t file_size = segment_buffer_.Size();
std::unique_ptr<File, FileCloser> segment_file;
Expand All @@ -195,14 +196,14 @@ Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) {
return Status(
error::FILE_FAILURE,
"Cannot close file " + segment_path +
", possibly file permission issue or running out of disk space.");
", possibly file permission issue or running out of disk space.");
}

if (listener_) {
listener_->OnNewSegment(segment_path,
start_timestamp * timescale_scale_ +
transport_stream_timestamp_offset_,
duration * timescale_scale_, file_size);
listener_->OnNewSegment(
segment_path,
start_timestamp * timescale_scale_ + transport_stream_timestamp_offset_,
duration * timescale_scale_, file_size);
}
segment_started_ = false;

Expand Down
10 changes: 7 additions & 3 deletions packager/media/formats/mp2t/ts_writer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,12 @@ bool WritePesToBuffer(const PesPacket& pes,
} // namespace

TsWriter::TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer)
: pmt_writer_(std::move(pmt_writer)) {}
: TsWriter(std::move(pmt_writer), 0) {}

TsWriter::TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer,
unsigned int segment_number)
: pat_continuity_counter_(ContinuityCounter(segment_number)),
pmt_writer_(std::move(pmt_writer)) {}

TsWriter::~TsWriter() {}

Expand All @@ -188,8 +193,7 @@ void TsWriter::SignalEncrypted() {
}

bool TsWriter::AddPesPacket(std::unique_ptr<PesPacket> pes_packet,
BufferWriter* buffer) {

BufferWriter* buffer) {
if (!WritePesToBuffer(*pes_packet, &elementary_stream_continuity_counter_,
buffer)) {
LOG(ERROR) << "Failed to write pes to buffer.";
Expand Down
9 changes: 8 additions & 1 deletion packager/media/formats/mp2t/ts_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ class ProgramMapTableWriter;
/// the data to file. This also creates PSI from StreamInfo.
class TsWriter {
public:
explicit TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer);
TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer);

/// Create TsWriter with segment_number
/// @param pmt_writer the writes PMT into ts packets
//// @param segment_number is used to set the continuity counter for PAT
/// packets.
TsWriter(std::unique_ptr<ProgramMapTableWriter> pmt_writer,
unsigned int segment_number);
lfordyce marked this conversation as resolved.
Show resolved Hide resolved
virtual ~TsWriter();

/// This will fail if the current segment is not finalized.
Expand Down
Loading