Shaka Packager SDK
ts_segmenter.cc
1 // Copyright 2016 Google LLC. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file or at
5 // https://developers.google.com/open-source/licenses/bsd
6 
7 #include <packager/media/formats/mp2t/ts_segmenter.h>
8 
9 #include <memory>
10 
11 #include <absl/log/check.h>
12 
13 #include <packager/macros/status.h>
14 #include <packager/media/base/audio_stream_info.h>
15 #include <packager/media/base/muxer_util.h>
16 #include <packager/media/base/video_stream_info.h>
17 #include <packager/media/event/muxer_listener.h>
18 #include <packager/media/formats/mp2t/pes_packet.h>
19 #include <packager/media/formats/mp2t/program_map_table_writer.h>
20 #include <packager/status.h>
21 
22 namespace shaka {
23 namespace media {
24 namespace mp2t {
25 
26 namespace {
27 const double kTsTimescale = 90000;
28 
29 bool IsAudioCodec(Codec codec) {
30  return codec >= kCodecAudio && codec < kCodecAudioMaxPlusOne;
31 }
32 
33 bool IsVideoCodec(Codec codec) {
34  return codec >= kCodecVideo && codec < kCodecVideoMaxPlusOne;
35 }
36 
37 } // namespace
38 
40  : listener_(listener),
41  transport_stream_timestamp_offset_(
42  options.transport_stream_timestamp_offset_ms * kTsTimescale / 1000),
43  pes_packet_generator_(
44  new PesPacketGenerator(transport_stream_timestamp_offset_)) {}
45 
46 TsSegmenter::~TsSegmenter() {}
47 
48 Status TsSegmenter::Initialize(const StreamInfo& stream_info) {
49  if (!pes_packet_generator_->Initialize(stream_info)) {
50  return Status(error::MUXER_FAILURE,
51  "Failed to initialize PesPacketGenerator.");
52  }
53 
54  const StreamType stream_type = stream_info.stream_type();
55  if (stream_type != StreamType::kStreamVideo &&
56  stream_type != StreamType::kStreamAudio) {
57  LOG(ERROR) << "TsWriter cannot handle stream type " << stream_type
58  << " yet.";
59  return Status(error::MUXER_FAILURE, "Unsupported stream type.");
60  }
61 
62  codec_ = stream_info.codec();
63  if (stream_type == StreamType::kStreamAudio)
64  audio_codec_config_ = stream_info.codec_config();
65 
66  timescale_scale_ = kTsTimescale / stream_info.time_scale();
67  return Status::OK;
68 }
69 
71  return Status::OK;
72 }
73 
74 Status TsSegmenter::AddSample(const MediaSample& sample) {
75  if (!ts_writer_) {
76  std::unique_ptr<ProgramMapTableWriter> pmt_writer;
77  if (codec_ == kCodecAC3) {
78  // https://goo.gl/N7Tvqi MPEG-2 Stream Encryption Format for HTTP Live
79  // Streaming 2.3.2.2 AC-3 Setup: For AC-3, the setup_data in the
80  // audio_setup_information is the first 10 bytes of the audio data (the
81  // syncframe()).
82  // For unencrypted AC3, the setup_data is not used, so what is in there
83  // does not matter.
84  const size_t kSetupDataSize = 10u;
85  if (sample.data_size() < kSetupDataSize) {
86  LOG(ERROR) << "Sample is too small for AC3: " << sample.data_size();
87  return Status(error::MUXER_FAILURE, "Sample is too small for AC3.");
88  }
89  const std::vector<uint8_t> setup_data(sample.data(),
90  sample.data() + kSetupDataSize);
91  pmt_writer.reset(new AudioProgramMapTableWriter(codec_, setup_data));
92  } else if (IsAudioCodec(codec_)) {
93  pmt_writer.reset(
94  new AudioProgramMapTableWriter(codec_, audio_codec_config_));
95  } else {
96  DCHECK(IsVideoCodec(codec_));
97  pmt_writer.reset(new VideoProgramMapTableWriter(codec_));
98  }
99  ts_writer_.reset(new TsWriter(std::move(pmt_writer)));
100  }
101 
102  if (sample.is_encrypted())
103  ts_writer_->SignalEncrypted();
104 
105  if (!segment_started_ && !sample.is_key_frame())
106  LOG(WARNING) << "A segment will start with a non key frame.";
107 
108  if (!pes_packet_generator_->PushSample(sample)) {
109  return Status(error::MUXER_FAILURE,
110  "Failed to add sample to PesPacketGenerator.");
111  }
112  return WritePesPackets();
113 }
114 
115 void TsSegmenter::InjectTsWriterForTesting(std::unique_ptr<TsWriter> writer) {
116  ts_writer_ = std::move(writer);
117 }
118 
120  std::unique_ptr<PesPacketGenerator> generator) {
121  pes_packet_generator_ = std::move(generator);
122 }
123 
125  segment_started_ = value;
126 }
127 
128 Status TsSegmenter::StartSegmentIfNeeded(int64_t next_pts) {
129  if (segment_started_)
130  return Status::OK;
131  segment_start_timestamp_ = next_pts;
132  if (!ts_writer_->NewSegment(&segment_buffer_))
133  return Status(error::MUXER_FAILURE, "Failed to initialize new segment.");
134  segment_started_ = true;
135  return Status::OK;
136 }
137 
138 Status TsSegmenter::WritePesPackets() {
139  while (pes_packet_generator_->NumberOfReadyPesPackets() > 0u) {
140  std::unique_ptr<PesPacket> pes_packet =
141  pes_packet_generator_->GetNextPesPacket();
142 
143  Status status = StartSegmentIfNeeded(pes_packet->pts());
144  if (!status.ok())
145  return status;
146 
147  if (listener_ && IsVideoCodec(codec_) && pes_packet->is_key_frame()) {
148 
149  uint64_t start_pos = segment_buffer_.Size();
150  const int64_t timestamp = pes_packet->pts();
151  if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_))
152  return Status(error::MUXER_FAILURE, "Failed to add PES packet.");
153 
154  uint64_t end_pos = segment_buffer_.Size();
155 
156  listener_->OnKeyFrame(timestamp, start_pos, end_pos - start_pos);
157  } else {
158  if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_))
159  return Status(error::MUXER_FAILURE, "Failed to add PES packet.");
160  }
161  }
162  return Status::OK;
163 }
164 
165 Status TsSegmenter::FinalizeSegment(int64_t start_timestamp, int64_t duration) {
166  if (!pes_packet_generator_->Flush()) {
167  return Status(error::MUXER_FAILURE, "Failed to flush PesPacketGenerator.");
168  }
169  Status status = WritePesPackets();
170  if (!status.ok())
171  return status;
172  return Status::OK;
173 }
174 
175 } // namespace mp2t
176 } // namespace media
177 } // namespace shaka
Class to hold a media sample.
Definition: media_sample.h:25
virtual void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size)=0
Abstract class holds stream information.
Definition: stream_info.h:71
ProgramMapTableWriter for video codecs.
Status FinalizeSegment(int64_t start_timestamp, int64_t duration)
void SetSegmentStartedForTesting(bool value)
Only for testing.
Status Initialize(const StreamInfo &stream_info)
Definition: ts_segmenter.cc:48
void InjectPesPacketGeneratorForTesting(std::unique_ptr< PesPacketGenerator > generator)
Only for testing.
Status AddSample(const MediaSample &sample)
Definition: ts_segmenter.cc:74
void InjectTsWriterForTesting(std::unique_ptr< TsWriter > writer)
Only for testing.
TsSegmenter(const MuxerOptions &options, MuxerListener *listener)
Definition: ts_segmenter.cc:39
ProgramMapTableWriter for video codecs.
All the methods that are virtual are virtual for mocking.
Definition: crypto_flags.cc:66
This structure contains the list of configuration options for Muxer.
Definition: muxer_options.h:19