blob: 87041a8e92cfa11233a69e1836eb7bbdaf44f9fd [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/base/stream_parser.h"
#include "media/base/stream_parser_buffer.h"
namespace media {
StreamParser::InitParameters::InitParameters(base::TimeDelta duration)
: duration(duration),
liveness(DemuxerStream::LIVENESS_UNKNOWN),
detected_audio_track_count(0),
detected_video_track_count(0),
detected_text_track_count(0) {}
StreamParser::StreamParser() = default;
StreamParser::~StreamParser() = default;
// Default implementation of ProcessChunks() is not fully implemented.
bool StreamParser::ProcessChunks(std::unique_ptr<BufferQueue> buffer_queue) {
NOTIMPLEMENTED(); // Likely the wrong type of parser is being used.
return false;
}
static bool MergeBufferQueuesInternal(
const std::vector<const StreamParser::BufferQueue*>& buffer_queues,
StreamParser::BufferQueue* merged_buffers) {
// Instead of std::merge usage, this method implements a custom merge because:
// 1) |buffer_queues| may contain N queues,
// 2) we must detect and return false if any of the queues in |buffer_queues|
// is unsorted, and
// 3) we must detect and return false if any of the buffers in |buffer_queues|
// has a decode timestamp prior to the last, if any, buffer in
// |merged_buffers|.
// TODO(wolenetz/acolwell): Refactor stream parsers to eliminate need for
// this large grain merge. See http://crbug.com/338484.
// Done if no inputs to merge.
if (buffer_queues.empty())
return true;
// Build a vector of iterators, one for each input, to traverse inputs.
// The union of these iterators points to the set of candidate buffers
// for being appended to |merged_buffers|.
size_t num_itrs = buffer_queues.size();
std::vector<StreamParser::BufferQueue::const_iterator> itrs(num_itrs);
for (size_t i = 0; i < num_itrs; ++i)
itrs[i] = buffer_queues[i]->begin();
// |last_decode_timestamp| tracks the lower bound, if any, that all candidate
// buffers must not be less than. If |merged_buffers| already has buffers,
// initialize |last_decode_timestamp| to the decode timestamp of the last
// buffer in it.
DecodeTimestamp last_decode_timestamp = kNoDecodeTimestamp();
if (!merged_buffers->empty())
last_decode_timestamp = merged_buffers->back()->GetDecodeTimestamp();
// Repeatedly select and append the next buffer from the candidate buffers
// until either:
// 1) returning false, to indicate detection of decreasing DTS in some queue,
// when a candidate buffer has decode timestamp below
// |last_decode_timestamp|, which means either an input buffer wasn't
// sorted correctly or had a buffer with decode timestamp below the last
// buffer, if any, in |merged_buffers|, or
// 2) returning true when all buffers have been merged successfully;
// equivalently, when all of the iterators in |itrs| have reached the end
// of their respective queue from |buffer_queues|.
// TODO(wolenetz/acolwell): Ideally, we would use a heap to store the head of
// all queues and pop the head with lowest decode timestamp in log(N) time.
// However, N will typically be small and usage of this implementation is
// meant to be short-term. See http://crbug.com/338484.
while (true) {
// Tracks which queue's iterator is pointing to the candidate buffer to
// append next, or -1 if no candidate buffers found. This indexes |itrs|.
int index_of_queue_with_next_decode_timestamp = -1;
DecodeTimestamp next_decode_timestamp = kNoDecodeTimestamp();
// Scan each of the iterators for |buffer_queues| to find the candidate
// buffer, if any, that has the lowest decode timestamp.
for (size_t i = 0; i < num_itrs; ++i) {
if (itrs[i] == buffer_queues[i]->end())
continue;
// Extract the candidate buffer's decode timestamp.
DecodeTimestamp ts = (*itrs[i])->GetDecodeTimestamp();
if (last_decode_timestamp != kNoDecodeTimestamp() &&
ts < last_decode_timestamp)
return false;
if (ts < next_decode_timestamp ||
next_decode_timestamp == kNoDecodeTimestamp()) {
// Remember the decode timestamp and queue iterator index for this
// potentially winning candidate buffer.
next_decode_timestamp = ts;
index_of_queue_with_next_decode_timestamp = i;
}
}
// All done if no further candidate buffers exist.
if (index_of_queue_with_next_decode_timestamp == -1)
return true;
// Otherwise, append the winning candidate buffer to |merged_buffers|,
// remember its decode timestamp as |last_decode_timestamp| now that it is
// the last buffer in |merged_buffers|, advance the corresponding
// input BufferQueue iterator, and continue.
scoped_refptr<StreamParserBuffer> buffer =
*itrs[index_of_queue_with_next_decode_timestamp];
last_decode_timestamp = buffer->GetDecodeTimestamp();
merged_buffers->push_back(buffer);
++itrs[index_of_queue_with_next_decode_timestamp];
}
}
bool MergeBufferQueues(const StreamParser::BufferQueueMap& buffer_queue_map,
StreamParser::BufferQueue* merged_buffers) {
DCHECK(merged_buffers);
// Prepare vector containing pointers to any provided non-empty buffer queues.
// Append audio buffer queues first, before all other types, since
// FrameProcessorTest.AudioVideo_Discontinuity currently depends on audio
// buffers being processed first.
std::vector<const StreamParser::BufferQueue*> buffer_queues;
for (const auto& it : buffer_queue_map) {
DCHECK(!it.second.empty());
if (it.second[0]->type() == DemuxerStream::AUDIO)
buffer_queues.push_back(&it.second);
}
for (const auto& it : buffer_queue_map) {
DCHECK(!it.second.empty());
if (it.second[0]->type() != DemuxerStream::AUDIO)
buffer_queues.push_back(&it.second);
}
// Do the merge.
return MergeBufferQueuesInternal(buffer_queues, merged_buffers);
}
} // namespace media