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