DASH Media Packaging SDK
 All Classes Namespaces Functions Variables Typedefs
single_segment_segmenter.cc
1 // Copyright 2014 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/mp4/single_segment_segmenter.h"
8 
9 #include <inttypes.h>
10 #include <openssl/err.h>
11 #include <openssl/rand.h>
12 
13 #include "packager/base/file_util.h"
14 #include "packager/base/strings/stringprintf.h"
15 #include "packager/base/threading/platform_thread.h"
16 #include "packager/base/time/time.h"
17 #include "packager/media/base/buffer_writer.h"
18 #include "packager/media/base/media_stream.h"
19 #include "packager/media/base/muxer_options.h"
20 #include "packager/media/event/muxer_listener.h"
21 #include "packager/media/event/progress_listener.h"
22 #include "packager/media/file/file.h"
23 #include "packager/media/formats/mp4/box_definitions.h"
24 
25 namespace edash_packager {
26 namespace media {
27 namespace mp4 {
28 namespace {
29 // Create a temp file name using process/thread id and current time.
30 std::string TempFileName() {
31  int32_t tid = static_cast<int32_t>(base::PlatformThread::CurrentId());
32  int64_t rand = 0;
33  if (RAND_bytes(reinterpret_cast<uint8_t*>(&rand), sizeof(rand)) != 1) {
34  LOG(WARNING) << "RAND_bytes failed with error: "
35  << ERR_error_string(ERR_get_error(), NULL);
36  rand = base::Time::Now().ToInternalValue();
37  }
38  return base::StringPrintf("packager-tempfile-%x-%" PRIx64, tid, rand);
39 }
40 } // namespace
41 
42 SingleSegmentSegmenter::SingleSegmentSegmenter(const MuxerOptions& options,
43  scoped_ptr<FileType> ftyp,
44  scoped_ptr<Movie> moov)
45  : Segmenter(options, ftyp.Pass(), moov.Pass()) {}
46 
47 SingleSegmentSegmenter::~SingleSegmentSegmenter() {
48  if (temp_file_)
49  temp_file_.release()->Close();
50  if (!temp_file_name_.empty()) {
51  if (!File::Delete(temp_file_name_.c_str()))
52  LOG(ERROR) << "Unable to delete temporary file " << temp_file_name_;
53  }
54 }
55 
56 bool SingleSegmentSegmenter::GetInitRange(size_t* offset, size_t* size) {
57  // In Finalize, ftyp and moov gets written first so offset must be 0.
58  *offset = 0;
59  *size = ftyp()->ComputeSize() + moov()->ComputeSize();
60  return true;
61 }
62 
63 bool SingleSegmentSegmenter::GetIndexRange(size_t* offset, size_t* size) {
64  // Index range is right after init range so the offset must be the size of
65  // ftyp and moov.
66  *offset = ftyp()->ComputeSize() + moov()->ComputeSize();
67  *size = vod_sidx_->ComputeSize();
68  return true;
69 }
70 
71 Status SingleSegmentSegmenter::DoInitialize() {
72  // Single segment segmentation involves two stages:
73  // Stage 1: Create media subsegments from media samples
74  // Stage 2: Update media header (moov) which involves copying of media
75  // subsegments
76  // Assumes stage 2 takes similar amount of time as stage 1. The previous
77  // progress_target was set for stage 1. Times two to account for stage 2.
78  set_progress_target(progress_target() * 2);
79 
80  if (options().temp_dir.empty()) {
81  base::FilePath temp_file_path;
82  if (!base::CreateTemporaryFile(&temp_file_path)) {
83  LOG(ERROR) << "Failed to create temporary file.";
84  return Status(error::FILE_FAILURE, "Unable to create temporary file.");
85  }
86  temp_file_name_ = temp_file_path.value();
87  } else {
88  temp_file_name_ =
89  base::FilePath(options().temp_dir).Append(TempFileName()).value();
90  }
91  temp_file_.reset(File::Open(temp_file_name_.c_str(), "w"));
92  return temp_file_
93  ? Status::OK
94  : Status(error::FILE_FAILURE,
95  "Cannot open file to write " + temp_file_name_);
96 }
97 
98 Status SingleSegmentSegmenter::DoFinalize() {
99  DCHECK(temp_file_);
100  DCHECK(ftyp());
101  DCHECK(moov());
102  DCHECK(vod_sidx_);
103 
104  // Close the temp file to prepare for reading later.
105  if (!temp_file_.release()->Close()) {
106  return Status(error::FILE_FAILURE,
107  "Cannot close the temp file " + temp_file_name_);
108  }
109 
110  scoped_ptr<File, FileCloser> file(
111  File::Open(options().output_file_name.c_str(), "w"));
112  if (file == NULL) {
113  return Status(error::FILE_FAILURE,
114  "Cannot open file to write " + options().output_file_name);
115  }
116 
117  LOG(INFO) << "Update media header (moov) and rewrite the file to '"
118  << options().output_file_name << "'.";
119 
120  // Write ftyp, moov and sidx to output file.
121  scoped_ptr<BufferWriter> buffer(new BufferWriter());
122  ftyp()->Write(buffer.get());
123  moov()->Write(buffer.get());
124  vod_sidx_->Write(buffer.get());
125  Status status = buffer->WriteToFile(file.get());
126  if (!status.ok())
127  return status;
128 
129  // Load the temp file and write to output file.
130  scoped_ptr<File, FileCloser> temp_file(
131  File::Open(temp_file_name_.c_str(), "r"));
132  if (temp_file == NULL) {
133  return Status(error::FILE_FAILURE,
134  "Cannot open file to read " + temp_file_name_);
135  }
136 
137  // The target of 2nd stage of single segment segmentation.
138  const uint64_t re_segment_progress_target = progress_target() * 0.5;
139 
140  const int kBufSize = 0x200000; // 2MB.
141  scoped_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
142  while (true) {
143  int64_t size = temp_file->Read(buf.get(), kBufSize);
144  if (size == 0) {
145  break;
146  } else if (size < 0) {
147  return Status(error::FILE_FAILURE,
148  "Failed to read file " + temp_file_name_);
149  }
150  int64_t size_written = file->Write(buf.get(), size);
151  if (size_written != size) {
152  return Status(error::FILE_FAILURE,
153  "Failed to write file " + options().output_file_name);
154  }
155  UpdateProgress(static_cast<double>(size) / temp_file->Size() *
156  re_segment_progress_target);
157  }
158  SetComplete();
159  return Status::OK;
160 }
161 
162 Status SingleSegmentSegmenter::DoFinalizeSegment() {
163  DCHECK(sidx());
164  DCHECK(fragment_buffer());
165  // sidx() contains pre-generated segment references with one reference per
166  // fragment. In VOD, this segment is converted into a subsegment, i.e. one
167  // reference, which contains all the fragments in sidx().
168  std::vector<SegmentReference>& refs = sidx()->references;
169  SegmentReference& vod_ref = refs[0];
170  uint64_t first_sap_time =
171  refs[0].sap_delta_time + refs[0].earliest_presentation_time;
172  for (uint32_t i = 1; i < refs.size(); ++i) {
173  vod_ref.referenced_size += refs[i].referenced_size;
174  // NOTE: We calculate subsegment duration based on the total duration of
175  // this subsegment instead of subtracting earliest_presentation_time as
176  // indicated in the spec.
177  vod_ref.subsegment_duration += refs[i].subsegment_duration;
178  vod_ref.earliest_presentation_time = std::min(
179  vod_ref.earliest_presentation_time, refs[i].earliest_presentation_time);
180 
181  if (vod_ref.sap_type == SegmentReference::TypeUnknown &&
182  refs[i].sap_type != SegmentReference::TypeUnknown) {
183  vod_ref.sap_type = refs[i].sap_type;
184  first_sap_time =
185  refs[i].sap_delta_time + refs[i].earliest_presentation_time;
186  }
187  }
188  // Calculate sap delta time w.r.t. earliest_presentation_time.
189  if (vod_ref.sap_type != SegmentReference::TypeUnknown) {
190  vod_ref.sap_delta_time =
191  first_sap_time - vod_ref.earliest_presentation_time;
192  }
193 
194  // Create segment if it does not exist yet.
195  if (vod_sidx_ == NULL) {
196  vod_sidx_.reset(new SegmentIndex());
197  vod_sidx_->reference_id = sidx()->reference_id;
198  vod_sidx_->timescale = sidx()->timescale;
199 
200  if (vod_ref.earliest_presentation_time > 0) {
201  const double starting_time_in_seconds =
202  static_cast<double>(vod_ref.earliest_presentation_time) /
203  GetReferenceTimeScale();
204  // Give a warning if it is significant.
205  if (starting_time_in_seconds > 0.5) {
206  // Note that DASH IF player requires presentationTimeOffset to be set in
207  // Segment{Base,List,Template} if there is non-zero starting time. Since
208  // current Chromium's MSE implementation uses DTS, the player expects
209  // DTS to be used.
210  LOG(WARNING) << "Warning! Non-zero starting time (in seconds): "
211  << starting_time_in_seconds
212  << ". Manual adjustment of presentationTimeOffset in "
213  "mpd might be necessary.";
214  }
215  }
216  // Force earliest_presentation_time to start from 0 for VOD.
217  vod_sidx_->earliest_presentation_time = 0;
218  }
219  vod_sidx_->references.push_back(vod_ref);
220 
221  // Append fragment buffer to temp file.
222  size_t segment_size = fragment_buffer()->Size();
223  Status status = fragment_buffer()->WriteToFile(temp_file_.get());
224  if (!status.ok()) return status;
225 
226  UpdateProgress(vod_ref.subsegment_duration);
227  if (muxer_listener()) {
228  muxer_listener()->OnSampleDurationReady(sample_duration());
229  muxer_listener()->OnNewSegment(vod_ref.earliest_presentation_time,
230  vod_ref.subsegment_duration, segment_size);
231  }
232  return Status::OK;
233 }
234 
235 } // namespace mp4
236 } // namespace media
237 } // namespace edash_packager
static bool Delete(const char *file_name)
Definition: file.cc:138
virtual bool Open()=0
Internal open. Should not be used directly.
void UpdateProgress(uint64_t progress)
Update segmentation progress using ProgressListener.
Definition: segmenter.cc:333
virtual bool GetInitRange(size_t *offset, size_t *size) OVERRIDE
void Write(BufferWriter *writer)
Definition: box.cc:25
void SetComplete()
Set progress to 100%.
Definition: segmenter.cc:349
virtual bool GetIndexRange(size_t *offset, size_t *size) OVERRIDE
virtual uint32_t ComputeSize() OVERRIDE
virtual uint32_t ComputeSize() OVERRIDE
virtual void OnSampleDurationReady(uint32_t sample_duration)=0