Shaka Packager SDK
Loading...
Searching...
No Matches
muxer_util.cc
1// Copyright 2014 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/base/muxer_util.h>
8
9#include <cinttypes>
10#include <string>
11#include <vector>
12
13#include <absl/log/check.h>
14#include <absl/log/log.h>
15#include <absl/strings/numbers.h>
16#include <absl/strings/str_format.h>
17#include <absl/strings/str_split.h>
18
19#include <packager/media/base/video_stream_info.h>
20
21namespace shaka {
22namespace {
23Status ValidateFormatTag(const std::string& format_tag) {
24 if (format_tag.empty()) {
25 return Status(error::INVALID_ARGUMENT, "Format tag should not be empty");
26 }
27
28 // Format tag should follow this prototype: %0[width]d.
29 if (format_tag.size() > 3 && format_tag[0] == '%' && format_tag[1] == '0' &&
30 format_tag[format_tag.size() - 1] == 'd') {
31 unsigned out;
32 if (absl::SimpleAtoi(format_tag.substr(2, format_tag.size() - 3), &out)) {
33 return Status::OK;
34 }
35 }
36
37 return Status(
38 error::INVALID_ARGUMENT,
39 "Format tag should follow this prototype: %0[width]d if exist.");
40}
41} // namespace
42
43namespace media {
44
45Status ValidateSegmentTemplate(const std::string& segment_template) {
46 if (segment_template.empty()) {
47 return Status(error::INVALID_ARGUMENT,
48 "Segment template should not be empty.");
49 }
50
51 std::vector<std::string> splits = absl::StrSplit(segment_template, "$");
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
111std::string GetSegmentName(const std::string& segment_template,
112 int64_t segment_start_time,
113 uint32_t segment_number,
114 uint32_t bandwidth) {
115 DCHECK_EQ(Status::OK, ValidateSegmentTemplate(segment_template));
116
117 std::vector<std::string> splits = absl::StrSplit(segment_template, "$");
118 // "$" always appears in pairs, so there should be odd number of splits.
119 DCHECK_EQ(1u, splits.size() % 2);
120
121 std::string segment_name;
122 for (size_t i = 0; i < splits.size(); ++i) {
123 // Every second substring in split output should be an identifier.
124 // Simply copy the non-identifier part.
125 if (i % 2 == 0) {
126 segment_name += splits[i];
127 continue;
128 }
129 if (splits[i].empty()) {
130 // "$$" is an escape sequence, replaced with a single "$".
131 segment_name += "$";
132 continue;
133 }
134 size_t format_pos = splits[i].find('%');
135 std::string identifier = splits[i].substr(0, format_pos);
136 DCHECK(identifier == "Number" || identifier == "Time" ||
137 identifier == "Bandwidth");
138
139 std::string format_tag;
140 if (format_pos != std::string::npos) {
141 format_tag = splits[i].substr(format_pos);
142 DCHECK_EQ(Status::OK, ValidateFormatTag(format_tag));
143 // Replace %d formatting to correctly format uint64_t.
144 format_tag = format_tag.substr(0, format_tag.size() - 1) + PRIu64;
145 } else {
146 // Default format tag "%01d", modified to format uint64_t correctly.
147 format_tag = "%01" PRIu64;
148 }
149
150 // absl::StrFormat requires compile-time constants for format strings.
151 // If you don't have that, you build the formatter using this interface
152 // instead.
153 std::vector<absl::FormatArg> format_args;
154 absl::UntypedFormatSpec format(format_tag);
155 if (identifier == "Number") {
156 // SegmentNumber starts from 1.
157 format_args.emplace_back(static_cast<uint64_t>(segment_number));
158 } else if (identifier == "Time") {
159 format_args.emplace_back(static_cast<uint64_t>(segment_start_time));
160 } else if (identifier == "Bandwidth") {
161 format_args.emplace_back(static_cast<uint64_t>(bandwidth));
162 }
163
164 std::string format_output;
165 if (absl::FormatUntyped(&format_output, format, format_args)) {
166 segment_name += format_output;
167 }
168 }
169 return segment_name;
170}
171
172} // namespace media
173} // namespace shaka
All the methods that are virtual are virtual for mocking.