Shaka Packager SDK
muxer_util.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/base/muxer_util.h"
8 
9 #include <inttypes.h>
10 
11 #include <string>
12 #include <vector>
13 
14 #include "packager/base/logging.h"
15 #include "packager/base/strings/string_number_conversions.h"
16 #include "packager/base/strings/string_split.h"
17 #include "packager/base/strings/stringprintf.h"
18 #include "packager/media/base/video_stream_info.h"
19 
20 namespace shaka {
21 namespace {
22 Status ValidateFormatTag(const std::string& format_tag) {
23  if (format_tag.empty()) {
24  return Status(error::INVALID_ARGUMENT, "Format tag should not be empty");
25  }
26 
27  // Format tag should follow this prototype: %0[width]d.
28  if (format_tag.size() > 3 && format_tag[0] == '%' && format_tag[1] == '0' &&
29  format_tag[format_tag.size() - 1] == 'd') {
30  unsigned out;
31  if (base::StringToUint(format_tag.substr(2, format_tag.size() - 3), &out)) {
32  return Status::OK;
33  }
34  }
35 
36  return Status(
37  error::INVALID_ARGUMENT,
38  "Format tag should follow this prototype: %0[width]d if exist.");
39 }
40 } // namespace
41 
42 namespace media {
43 
44 Status ValidateSegmentTemplate(const std::string& segment_template) {
45  if (segment_template.empty()) {
46  return Status(error::INVALID_ARGUMENT,
47  "Segment template should not be empty.");
48  }
49 
50  std::vector<std::string> splits = base::SplitString(
51  segment_template, "$", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
52 
53  // ISO/IEC 23009-1:2012 5.3.9.4.4 Template-based Segment URL construction.
54  // Allowed identifiers: $$, $RepresentationID$, $Number$, $Bandwidth$, $Time$.
55  // "$" always appears in pairs, so there should be odd number of splits.
56  if (splits.size() % 2 == 0) {
57  return Status(error::INVALID_ARGUMENT,
58  "In segment templates, '$' should appear in pairs.");
59  }
60 
61  bool has_number = false;
62  bool has_time = false;
63  // Every second substring in split output should be an identifier.
64  for (size_t i = 1; i < splits.size(); i += 2) {
65  // Each identifier may be suffixed, within the enclosing ‘$’ characters,
66  // with an additional format tag aligned with the printf format tag as
67  // defined in IEEE 1003.1-2008 [10] following this prototype: %0[width]d.
68  size_t format_pos = splits[i].find('%');
69  std::string identifier = splits[i].substr(0, format_pos);
70  if (format_pos != std::string::npos) {
71  Status tag_check = ValidateFormatTag(splits[i].substr(format_pos));
72  if (!tag_check.ok()) {
73  return tag_check;
74  }
75  }
76 
77  // TODO(kqyang): Support "RepresentationID".
78  if (identifier == "RepresentationID") {
79  return Status(
80  error::UNIMPLEMENTED,
81  "Segment template flag $RepresentationID$ is not supported yet.");
82  } else if (identifier == "Number") {
83  has_number = true;
84  } else if (identifier == "Time") {
85  has_time = true;
86  } else if (identifier == "") {
87  if (format_pos != std::string::npos) {
88  return Status(error::INVALID_ARGUMENT,
89  "'$$' should not have any format tags.");
90  }
91  } else if (identifier != "Bandwidth") {
92  return Status(error::INVALID_ARGUMENT,
93  "'$" + splits[i] + "$' is not a valid identifier.");
94  }
95  }
96  if (has_number && has_time) {
97  return Status(
98  error::INVALID_ARGUMENT,
99  "In segment templates $Number$ and $Time$ should not co-exist.");
100  }
101  if (!has_number && !has_time) {
102  return Status(error::INVALID_ARGUMENT,
103  "In segment templates $Number$ or $Time$ should exist.");
104  }
105  // Note: The below check is skipped.
106  // Strings outside identifiers shall only contain characters that are
107  // permitted within URLs according to RFC 1738.
108  return Status::OK;
109 }
110 
111 std::string GetSegmentName(const std::string& segment_template,
112  uint64_t segment_start_time,
113  uint32_t segment_index,
114  uint32_t bandwidth) {
115  DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
116 
117  std::vector<std::string> splits = base::SplitString(
118  segment_template, "$", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
119  // "$" always appears in pairs, so there should be odd number of splits.
120  DCHECK_EQ(1u, splits.size() % 2);
121 
122  std::string segment_name;
123  for (size_t i = 0; i < splits.size(); ++i) {
124  // Every second substring in split output should be an identifier.
125  // Simply copy the non-identifier part.
126  if (i % 2 == 0) {
127  segment_name += splits[i];
128  continue;
129  }
130  if (splits[i].empty()) {
131  // "$$" is an escape sequence, replaced with a single "$".
132  segment_name += "$";
133  continue;
134  }
135  size_t format_pos = splits[i].find('%');
136  std::string identifier = splits[i].substr(0, format_pos);
137  DCHECK(identifier == "Number" || identifier == "Time" ||
138  identifier == "Bandwidth");
139 
140  std::string format_tag;
141  if (format_pos != std::string::npos) {
142  format_tag = splits[i].substr(format_pos);
143  DCHECK_EQ(Status::OK, ValidateFormatTag(format_tag));
144  // Replace %d formatting to correctly format uint64_t.
145  format_tag = format_tag.substr(0, format_tag.size() - 1) + PRIu64;
146  } else {
147  // Default format tag "%01d", modified to format uint64_t correctly.
148  format_tag = "%01" PRIu64;
149  }
150 
151  if (identifier == "Number") {
152  // SegmentNumber starts from 1.
153  segment_name += base::StringPrintf(
154  format_tag.c_str(), static_cast<uint64_t>(segment_index + 1));
155  } else if (identifier == "Time") {
156  segment_name +=
157  base::StringPrintf(format_tag.c_str(), segment_start_time);
158  } else if (identifier == "Bandwidth") {
159  segment_name += base::StringPrintf(format_tag.c_str(),
160  static_cast<uint64_t>(bandwidth));
161  }
162  }
163  return segment_name;
164 }
165 
166 } // namespace media
167 } // namespace shaka
All the methods that are virtual are virtual for mocking.