Shaka Packager SDK
ts_segmenter.cc
1 // Copyright 2016 Google Inc. 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 "packager/media/base/audio_stream_info.h"
12 #include "packager/media/base/muxer_util.h"
13 #include "packager/media/base/video_stream_info.h"
14 #include "packager/media/event/muxer_listener.h"
15 #include "packager/media/formats/mp2t/pes_packet.h"
16 #include "packager/media/formats/mp2t/program_map_table_writer.h"
17 #include "packager/status.h"
18 #include "packager/status_macros.h"
19 
20 namespace shaka {
21 namespace media {
22 namespace mp2t {
23 
24 namespace {
25 const double kTsTimescale = 90000;
26 
27 bool IsAudioCodec(Codec codec) {
28  return codec >= kCodecAudio && codec < kCodecAudioMaxPlusOne;
29 }
30 
31 bool IsVideoCodec(Codec codec) {
32  return codec >= kCodecVideo && codec < kCodecVideoMaxPlusOne;
33 }
34 
35 } // namespace
36 
38  : muxer_options_(options),
39  listener_(listener),
40  transport_stream_timestamp_offset_(
41  options.transport_stream_timestamp_offset_ms * kTsTimescale / 1000),
42  pes_packet_generator_(
43  new PesPacketGenerator(transport_stream_timestamp_offset_)) {}
44 
45 TsSegmenter::~TsSegmenter() {}
46 
48  if (muxer_options_.segment_template.empty())
49  return Status(error::MUXER_FAILURE, "Segment template not specified.");
50  if (!pes_packet_generator_->Initialize(stream_info)) {
51  return Status(error::MUXER_FAILURE,
52  "Failed to initialize PesPacketGenerator.");
53  }
54 
55  const StreamType stream_type = stream_info.stream_type();
56  if (stream_type != StreamType::kStreamVideo &&
57  stream_type != StreamType::kStreamAudio) {
58  LOG(ERROR) << "TsWriter cannot handle stream type " << stream_type
59  << " yet.";
60  return Status(error::MUXER_FAILURE, "Unsupported stream type.");
61  }
62 
63  codec_ = stream_info.codec();
64  if (stream_type == StreamType::kStreamAudio)
65  audio_codec_config_ = stream_info.codec_config();
66 
67  timescale_scale_ = kTsTimescale / stream_info.time_scale();
68  return Status::OK;
69 }
70 
72  return Status::OK;
73 }
74 
76  if (!ts_writer_) {
77  std::unique_ptr<ProgramMapTableWriter> pmt_writer;
78  if (codec_ == kCodecAC3) {
79  // https://goo.gl/N7Tvqi MPEG-2 Stream Encryption Format for HTTP Live
80  // Streaming 2.3.2.2 AC-3 Setup: For AC-3, the setup_data in the
81  // audio_setup_information is the first 10 bytes of the audio data (the
82  // syncframe()).
83  // For unencrypted AC3, the setup_data is not used, so what is in there
84  // does not matter.
85  const size_t kSetupDataSize = 10u;
86  if (sample.data_size() < kSetupDataSize) {
87  LOG(ERROR) << "Sample is too small for AC3: " << sample.data_size();
88  return Status(error::MUXER_FAILURE, "Sample is too small for AC3.");
89  }
90  const std::vector<uint8_t> setup_data(sample.data(),
91  sample.data() + kSetupDataSize);
92  pmt_writer.reset(new AudioProgramMapTableWriter(codec_, setup_data));
93  } else if (IsAudioCodec(codec_)) {
94  pmt_writer.reset(
95  new AudioProgramMapTableWriter(codec_, audio_codec_config_));
96  } else {
97  DCHECK(IsVideoCodec(codec_));
98  pmt_writer.reset(new VideoProgramMapTableWriter(codec_));
99  }
100  ts_writer_.reset(new TsWriter(std::move(pmt_writer)));
101  }
102 
103  if (sample.is_encrypted())
104  ts_writer_->SignalEncrypted();
105 
106  if (!segment_started_ && !sample.is_key_frame())
107  LOG(WARNING) << "A segment will start with a non key frame.";
108 
109  if (!pes_packet_generator_->PushSample(sample)) {
110  return Status(error::MUXER_FAILURE,
111  "Failed to add sample to PesPacketGenerator.");
112  }
113  return WritePesPackets();
114 }
115 
116 void TsSegmenter::InjectTsWriterForTesting(std::unique_ptr<TsWriter> writer) {
117  ts_writer_ = std::move(writer);
118 }
119 
121  std::unique_ptr<PesPacketGenerator> generator) {
122  pes_packet_generator_ = std::move(generator);
123 }
124 
126  segment_started_ = value;
127 }
128 
129 Status TsSegmenter::StartSegmentIfNeeded(int64_t next_pts) {
130  if (segment_started_)
131  return Status::OK;
132  segment_start_timestamp_ = next_pts;
133  if (!ts_writer_->NewSegment(&segment_buffer_))
134  return Status(error::MUXER_FAILURE, "Failed to initialize new segment.");
135  segment_started_ = true;
136  return Status::OK;
137 }
138 
139 Status TsSegmenter::WritePesPackets() {
140  while (pes_packet_generator_->NumberOfReadyPesPackets() > 0u) {
141  std::unique_ptr<PesPacket> pes_packet =
142  pes_packet_generator_->GetNextPesPacket();
143 
144  Status status = StartSegmentIfNeeded(pes_packet->pts());
145  if (!status.ok())
146  return status;
147 
148  if (listener_ && IsVideoCodec(codec_) && pes_packet->is_key_frame()) {
149 
150  uint64_t start_pos = segment_buffer_.Size();
151  const int64_t timestamp = pes_packet->pts();
152  if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_))
153  return Status(error::MUXER_FAILURE, "Failed to add PES packet.");
154 
155  uint64_t end_pos = segment_buffer_.Size();
156 
157  listener_->OnKeyFrame(timestamp, start_pos, end_pos - start_pos);
158  } else {
159  if (!ts_writer_->AddPesPacket(std::move(pes_packet), &segment_buffer_))
160  return Status(error::MUXER_FAILURE, "Failed to add PES packet.");
161  }
162  }
163  return Status::OK;
164 }
165 
166 Status TsSegmenter::FinalizeSegment(uint64_t start_timestamp,
167  uint64_t duration) {
168  if (!pes_packet_generator_->Flush()) {
169  return Status(error::MUXER_FAILURE, "Failed to flush PesPacketGenerator.");
170  }
171  Status status = WritePesPackets();
172  if (!status.ok())
173  return status;
174 
175  // This method may be called from Finalize() so segment_started_ could
176  // be false.
177  if (!segment_started_)
178  return Status::OK;
179  std::string segment_path =
180  GetSegmentName(muxer_options_.segment_template, segment_start_timestamp_,
181  segment_number_++, muxer_options_.bandwidth);
182 
183  const int64_t file_size = segment_buffer_.Size();
184  std::unique_ptr<File, FileCloser> segment_file;
185  segment_file.reset(File::Open(segment_path.c_str(), "w"));
186  if (!segment_file) {
187  return Status(error::FILE_FAILURE,
188  "Cannot open file for write " + segment_path);
189  }
190 
191  RETURN_IF_ERROR(segment_buffer_.WriteToFile(segment_file.get()));
192 
193  if (!segment_file.release()->Close()) {
194  return Status(
195  error::FILE_FAILURE,
196  "Cannot close file " + segment_path +
197  ", possibly file permission issue or running out of disk space.");
198  }
199 
200  if (listener_) {
201  listener_->OnNewSegment(segment_path,
202  start_timestamp * timescale_scale_ +
203  transport_stream_timestamp_offset_,
204  duration * timescale_scale_, file_size);
205  }
206  segment_started_ = false;
207 
208  return Status::OK;
209 }
210 
211 } // namespace mp2t
212 } // namespace media
213 } // namespace shaka
virtual bool Open()=0
Internal open. Should not be used directly.
Status WriteToFile(File *file)
Class to hold a media sample.
Definition: media_sample.h:22
virtual void OnNewSegment(const std::string &segment_name, int64_t start_time, int64_t duration, uint64_t segment_file_size)=0
virtual void OnKeyFrame(int64_t timestamp, uint64_t start_byte_offset, uint64_t size)=0
Abstract class holds stream information.
Definition: stream_info.h:65
ProgramMapTableWriter for video codecs.
void SetSegmentStartedForTesting(bool value)
Only for testing.
Status Initialize(const StreamInfo &stream_info)
Definition: ts_segmenter.cc:47
void InjectPesPacketGeneratorForTesting(std::unique_ptr< PesPacketGenerator > generator)
Only for testing.
Status FinalizeSegment(uint64_t start_timestamp, uint64_t duration)
Status AddSample(const MediaSample &sample)
Definition: ts_segmenter.cc:75
void InjectTsWriterForTesting(std::unique_ptr< TsWriter > writer)
Only for testing.
TsSegmenter(const MuxerOptions &options, MuxerListener *listener)
Definition: ts_segmenter.cc:37
ProgramMapTableWriter for video codecs.
All the methods that are virtual are virtual for mocking.
This structure contains the list of configuration options for Muxer.
Definition: muxer_options.h:20