1 // Copyright 2023 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/filters/hls_codec_detector.h"
6 #include "base/task/bind_post_task.h"
7 #include "media/formats/mp2t/mp2t_stream_parser.h"
8 #include "media/formats/mp4/mp4_stream_parser.h"
12 HlsCodecDetector::~HlsCodecDetector() = default;
13 HlsCodecDetectorImpl::~HlsCodecDetectorImpl() = default;
14 HlsCodecDetectorImpl::HlsCodecDetectorImpl(MediaLog* log,
15 HlsRenditionHost* host)
16 : log_(log->Clone()), rendition_host_(host) {
20 void HlsCodecDetectorImpl::DetermineContainerOnly(
21 std::unique_ptr<HlsDataSourceStream> stream,
23 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
25 callback_ = std::move(cb);
27 rendition_host_->ReadStream(
28 std::move(stream), base::BindOnce(&HlsCodecDetectorImpl::OnStreamFetched,
29 weak_factory_.GetWeakPtr(),
30 /*container_only=*/true));
33 void HlsCodecDetectorImpl::DetermineContainerAndCodec(
34 std::unique_ptr<HlsDataSourceStream> stream,
36 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
38 callback_ = std::move(cb);
40 rendition_host_->ReadStream(
41 std::move(stream), base::BindOnce(&HlsCodecDetectorImpl::OnStreamFetched,
42 weak_factory_.GetWeakPtr(),
43 /*container_only=*/false));
46 void HlsCodecDetectorImpl::OnStreamFetched(
48 HlsDataSourceProvider::ReadResult maybe_stream) {
49 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
52 if (!maybe_stream.has_value()) {
53 HlsDemuxerStatus status = {HlsDemuxerStatus::Codes::kPlaylistUrlInvalid,
54 std::move(maybe_stream).error()};
55 std::move(callback_).Run(std::move(status));
59 auto stream = std::move(maybe_stream).value();
60 auto data_size = stream->buffer_size();
62 // If no data came back, then the data source has been exhausted and we
63 // have failed to determine a codec.
64 std::move(callback_).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
68 // If this is the first call to `OnStreamFetched`, then `parser_` should be
69 // null, and needs to be created. If `DetermineContainer` fails to determine
70 // the container being used, then it will call `callback_` and not set
73 DetermineContainer(container_only, stream->raw_data(), data_size);
77 // `DetermineContainer` MUST execute `callback_` if it fails to create a
83 // `DetermineContainer` MUST set `container_` if it also sets `parser_`.
84 CHECK(!container_.empty());
87 // If we only want the container, don't parse any data, and just return
88 // the container with an empty codec string.
89 std::move(callback_).Run(ContainerAndCodecs{
90 .container = std::move(container_),
96 if (!parser_->AppendToParseBuffer(stream->raw_data(), data_size)) {
97 std::move(callback_).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
101 auto parse_result = StreamParser::ParseStatus::kSuccessHasMoreData;
102 while (StreamParser::ParseStatus::kSuccessHasMoreData == parse_result) {
103 parse_result = parser_->Parse(StreamParser::kMaxPendingBytesPerParse);
105 // The parser has triggered the codec callback and we no longer need to
112 if (StreamParser::ParseStatus::kFailed == parse_result) {
113 std::move(callback_).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
117 CHECK_EQ(StreamParser::ParseStatus::kSuccess, parse_result);
118 if (!stream->CanReadMore()) {
119 std::move(callback_).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
123 // If the existing data was parsed but parsing hasn't resulted in a successful
124 // detection, keep reading from the data source until it is exhausted. The
125 // HLS chunks are usually not too large, and playback will need to read this
126 // chunk initially anyway, so fetching the whole thing isn't going to be an
129 rendition_host_->ReadStream(
131 base::BindOnce(&HlsCodecDetectorImpl::OnStreamFetched,
132 weak_factory_.GetWeakPtr(), container_only));
135 void HlsCodecDetectorImpl::DetermineContainer(bool container_only,
138 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
139 constexpr uint8_t kMP4FirstByte = 0x66;
140 constexpr uint8_t kMPEGTSFirstByte = 0x47;
144 CHECK(container_.empty());
147 std::move(callback_).Run(HlsDemuxerStatus::Codes::kInvalidBitstream);
152 const std::vector<uint8_t> mp4 = {kMP4FirstByte, 0x74, 0x79, 0x70,
153 0x69, 0x73, 0x6F, 0x6D};
155 StreamParser::NewConfigCB on_container_configs;
157 case kMPEGTSFirstByte: {
158 container_ = "video/mp2t";
159 // The Mp2t parser wants a list of all codecs it's allowed to check for,
160 // which means we need one codec for H264, one for AAC, and one for MP3.
161 // It doesn't actually care about the codecs details like profile or
162 // level, so we can give it the most basic of each type, and it will still
163 // find the actual codecs present in the stream.
164 const std::string codecs[]{
165 "avc1.420000", // The H264 baseline bitstream codec
166 "aac", // This is sufficient to get the AAC codec.
167 "mp3", // This is sufficient to get the MP3 codec.
169 // TODO(crbug/1266991): The mp2t parser isn't able to determine whether
170 // aac audio codecs use sbr (aka double samples per second), so the parser
171 // will have to be modified in the future to detect that, and provide it
172 // so that we can determine it's presence.
174 std::make_unique<mp2t::Mp2tStreamParser>(base::span{codecs}, false);
175 on_container_configs = base::BindRepeating(
176 &HlsCodecDetectorImpl::OnNewConfigMP2T, base::Unretained(this));
179 case kMP4FirstByte: {
180 // TODO(crbug/1266991): Android Media Player doesn't currently support
181 // the fragmented mp4 playback case. We'd like to get there someday, but
182 // it's not on the initial roadmap. The fragmented mp4 container will
183 // start with the bytes 0x66 0x74 0x79 0x70 0x69 0x73 0x6F 0x6D, and we
184 // can check for that later.
185 std::move(callback_).Run(HlsDemuxerStatus::Codes::kUnsupportedContainer);
189 std::move(callback_).Run(HlsDemuxerStatus::Codes::kUnsupportedContainer);
194 if (container_only) {
195 // Don't initialize the parser when we only care about querying the
200 // `this` owns `parser_` and never transfers it, while `parser` owns these
201 // callbacks and never transfers them. It's therefore safe to use
202 // base::Unretained here, and we actually have to since weak_ptr + repeating
203 // callback + return type isn't allowed.
204 parser_->Init(base::DoNothingAs<void(const StreamParser::InitParameters&)>(),
205 std::move(on_container_configs),
206 base::BindRepeating(&HlsCodecDetectorImpl::OnNewBuffers,
207 base::Unretained(this)),
208 base::BindRepeating(&HlsCodecDetectorImpl::OnEncryptedMediaInit,
209 base::Unretained(this)),
210 base::DoNothing(), base::DoNothing(), log_.get());
213 void HlsCodecDetectorImpl::AddCodecToResponse(std::string codec) {
214 if (codec_response_ == "") {
215 codec_response_ = codec;
217 codec_response_ = codec_response_ + ", " + codec;
221 void HlsCodecDetectorImpl::ParserInit(
222 const StreamParser::InitParameters& params) {}
224 bool HlsCodecDetectorImpl::OnNewConfigMP2T(
225 std::unique_ptr<MediaTracks> tracks) {
226 for (const auto& [id, video_config] : tracks->GetVideoConfigs()) {
227 if (video_config.codec() != VideoCodec::kH264) {
228 HlsDemuxerStatus error = HlsDemuxerStatus::Codes::kUnsupportedCodec;
229 std::move(callback_).Run(
230 std::move(error).WithData("codec", video_config.codec()));
233 // Any avc1 codec will do, since the mp2t parser throws all the info away
234 // except for the codec type being h264.
235 AddCodecToResponse("avc1.420000");
238 for (const auto& [id, audio_config] : tracks->GetAudioConfigs()) {
239 if (audio_config.codec() == AudioCodec::kAAC) {
240 // Just use a dummy codec here for aac. The actual parser doesn't care
241 // when we start demuxing for real.
242 AddCodecToResponse("mp4a.40.05");
243 } else if (audio_config.codec() == AudioCodec::kMP3) {
244 AddCodecToResponse("mp3");
246 HlsDemuxerStatus error = HlsDemuxerStatus::Codes::kUnsupportedCodec;
247 std::move(callback_).Run(
248 std::move(error).WithData("codec", audio_config.codec()));
255 bool HlsCodecDetectorImpl::OnNewBuffers(
256 const StreamParser::BufferQueueMap& buffers) {
257 // Buffers come after all the configs, so once we hit the buffers, we can
258 // reply to `callback`. Move `codec_reponse_` and `container_` to clear them
259 // for the next parse.
260 std::move(callback_).Run(ContainerAndCodecs{
261 .container = std::move(container_),
262 .codecs = std::move(codec_response_),
267 void HlsCodecDetectorImpl::OnEncryptedMediaInit(
268 EmeInitDataType type,
269 const std::vector<uint8_t>& data) {
270 std::move(callback_).Run(
271 HlsDemuxerStatus::Codes::kEncryptedMediaNotSupported);