| // Copyright 2014 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <sstream> |
| |
| #include "base/macros.h" |
| #include "media/base/stream_parser.h" |
| #include "media/base/stream_parser_buffer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace media { |
| |
| typedef StreamParser::TrackId TrackId; |
| typedef StreamParser::BufferQueue BufferQueue; |
| |
| const int kEnd = -1; |
| const uint8_t kFakeData[] = {0xFF}; |
| const TrackId kAudioTrackId = 0; |
| const TrackId kVideoTrackId = 1; |
| const TrackId kTextTrackIdA = 2; |
| const TrackId kTextTrackIdB = 3; |
| |
| static bool IsAudio(scoped_refptr<StreamParserBuffer> buffer) { |
| return buffer->type() == DemuxerStream::AUDIO; |
| } |
| |
| static bool IsVideo(scoped_refptr<StreamParserBuffer> buffer) { |
| return buffer->type() == DemuxerStream::VIDEO; |
| } |
| |
| static bool IsText(scoped_refptr<StreamParserBuffer> buffer) { |
| return buffer->type() == DemuxerStream::TEXT; |
| } |
| |
| // Creates and appends a sequence of StreamParserBuffers to the provided |
| // |queue|. |decode_timestamps| determines the number of appended buffers and |
| // their sequence of decode timestamps; a |kEnd| timestamp indicates the |
| // end of the sequence and no buffer is appended for it. Each new buffer's |
| // type will be |type| with track ID set to |track_id|. |
| static void GenerateBuffers(const int* decode_timestamps, |
| StreamParserBuffer::Type type, |
| TrackId track_id, |
| BufferQueue* queue) { |
| DCHECK(decode_timestamps); |
| DCHECK(queue); |
| DCHECK_NE(type, DemuxerStream::UNKNOWN); |
| DCHECK_LE(type, DemuxerStream::TYPE_MAX); |
| for (int i = 0; decode_timestamps[i] != kEnd; ++i) { |
| scoped_refptr<StreamParserBuffer> buffer = |
| StreamParserBuffer::CopyFrom(kFakeData, sizeof(kFakeData), |
| true, type, track_id); |
| buffer->SetDecodeTimestamp( |
| DecodeTimestamp::FromMicroseconds(decode_timestamps[i])); |
| queue->push_back(buffer); |
| } |
| } |
| |
| class StreamParserTest : public testing::Test { |
| protected: |
| StreamParserTest() = default; |
| |
| // Returns the number of buffers in |merged_buffers_| for which |predicate| |
| // returns true. |
| size_t CountMatchingMergedBuffers( |
| bool (*predicate)(scoped_refptr<StreamParserBuffer> buffer)) { |
| return static_cast<size_t>(std::count_if(merged_buffers_.begin(), |
| merged_buffers_.end(), predicate)); |
| } |
| |
| // Appends test audio buffers in the sequence described by |decode_timestamps| |
| // to |audio_buffers_|. See GenerateBuffers() for |decode_timestamps| format. |
| void GenerateAudioBuffers(const int* decode_timestamps) { |
| GenerateBuffers(decode_timestamps, DemuxerStream::AUDIO, kAudioTrackId, |
| &buffer_queue_map_[kAudioTrackId]); |
| } |
| |
| // Appends test video buffers in the sequence described by |decode_timestamps| |
| // to |video_buffers_|. See GenerateBuffers() for |decode_timestamps| format. |
| void GenerateVideoBuffers(const int* decode_timestamps) { |
| GenerateBuffers(decode_timestamps, DemuxerStream::VIDEO, kVideoTrackId, |
| &buffer_queue_map_[kVideoTrackId]); |
| } |
| |
| // Current tests only need up to two distinct text BufferQueues. This helper |
| // conditionally appends buffers to the underlying |buffer_queue_map_| keyed |
| // by the respective track ID. If |decode_timestamps_{a,b}| |
| // is NULL, then the corresponding BufferQueue is not changed at all. |
| // Note that key collision on map insertion does not replace the previous |
| // value. |
| void GenerateTextBuffers(const int* decode_timestamps_a, |
| const int* decode_timestamps_b) { |
| if (decode_timestamps_a) { |
| GenerateBuffers(decode_timestamps_a, DemuxerStream::TEXT, kTextTrackIdA, |
| &buffer_queue_map_[kTextTrackIdA]); |
| } |
| |
| if (decode_timestamps_b) { |
| GenerateBuffers(decode_timestamps_b, DemuxerStream::TEXT, kTextTrackIdB, |
| &buffer_queue_map_[kTextTrackIdB]); |
| } |
| } |
| |
| // Returns a string that describes the sequence of buffers in |
| // |merged_buffers_|. The string is a concatenation of space-delimited buffer |
| // descriptors in the same sequence as |merged_buffers_|. Each descriptor is |
| // the concatenation of |
| // 1) a single character that describes the buffer's type(), e.g. A, V, or T |
| // for audio, video, or text, respectively |
| // 2) the buffer's track_id() |
| // 3) ":" |
| // 4) the buffer's decode timestamp. |
| // If |include_type_and_text_track| is false, then items 1, 2, and 3 are |
| // not included in descriptors. This is useful when buffers with different |
| // media types but the same decode timestamp are expected, and the exact |
| // sequence of media types for the tying timestamps is not subject to |
| // verification. |
| std::string MergedBufferQueueString(bool include_type_and_text_track) { |
| std::stringstream results_stream; |
| for (BufferQueue::const_iterator itr = merged_buffers_.begin(); |
| itr != merged_buffers_.end(); |
| ++itr) { |
| if (itr != merged_buffers_.begin()) |
| results_stream << " "; |
| const StreamParserBuffer& buffer = *(itr->get()); |
| if (include_type_and_text_track) { |
| switch (buffer.type()) { |
| case DemuxerStream::AUDIO: |
| results_stream << "A"; |
| break; |
| case DemuxerStream::VIDEO: |
| results_stream << "V"; |
| break; |
| case DemuxerStream::TEXT: |
| results_stream << "T"; |
| |
| break; |
| default: |
| NOTREACHED(); |
| } |
| results_stream << buffer.track_id() << ":"; |
| } |
| results_stream << buffer.GetDecodeTimestamp().InMicroseconds(); |
| } |
| |
| return results_stream.str(); |
| } |
| |
| // Verifies that MergeBufferQueues() of the current |audio_buffers_|, |
| // |video_buffers_|, |text_map_|, and |merged_buffers_| returns true and |
| // results in an updated |merged_buffers_| that matches expectation. The |
| // expectation, specified in |expected|, is compared to the string resulting |
| // from MergedBufferQueueString() (see comments for that method) with |
| // |verify_type_and_text_track_sequence| passed. |merged_buffers_| is appended |
| // to by the merge, and may be setup by the caller to have some pre-existing |
| // buffers; it is both an input and output of this method. |
| // Regardless of |verify_type_and_text_track_sequence|, the marginal number |
| // of buffers of each type (audio, video, text) resulting from the merge is |
| // also verified to match the number of buffers in |audio_buffers_|, |
| // |video_buffers_|, and |text_map_|, respectively. |
| void VerifyMergeSuccess(const std::string& expected, |
| bool verify_type_and_text_track_sequence) { |
| // |merged_buffers| may already have some buffers. Count them by type for |
| // later inclusion in verification. |
| size_t original_audio_in_merged = CountMatchingMergedBuffers(IsAudio); |
| size_t original_video_in_merged = CountMatchingMergedBuffers(IsVideo); |
| size_t original_text_in_merged = CountMatchingMergedBuffers(IsText); |
| |
| EXPECT_TRUE(MergeBufferQueues(buffer_queue_map_, &merged_buffers_)); |
| |
| // Verify resulting contents of |merged_buffers| matches |expected|. |
| EXPECT_EQ(expected, |
| MergedBufferQueueString(verify_type_and_text_track_sequence)); |
| |
| // Verify that the correct number of each type of buffer is in the merge |
| // result. |
| size_t audio_in_merged = CountMatchingMergedBuffers(IsAudio); |
| size_t video_in_merged = CountMatchingMergedBuffers(IsVideo); |
| size_t text_in_merged = CountMatchingMergedBuffers(IsText); |
| |
| EXPECT_GE(audio_in_merged, original_audio_in_merged); |
| EXPECT_GE(video_in_merged, original_video_in_merged); |
| EXPECT_GE(text_in_merged, original_text_in_merged); |
| |
| EXPECT_EQ(buffer_queue_map_[kAudioTrackId].size(), |
| audio_in_merged - original_audio_in_merged); |
| if (buffer_queue_map_[kAudioTrackId].empty()) |
| buffer_queue_map_.erase(kAudioTrackId); |
| EXPECT_EQ(buffer_queue_map_[kVideoTrackId].size(), |
| video_in_merged - original_video_in_merged); |
| if (buffer_queue_map_[kVideoTrackId].empty()) |
| buffer_queue_map_.erase(kVideoTrackId); |
| |
| size_t expected_text_buffer_count = 0; |
| expected_text_buffer_count += buffer_queue_map_[kTextTrackIdA].size(); |
| if (buffer_queue_map_[kTextTrackIdA].empty()) |
| buffer_queue_map_.erase(kTextTrackIdA); |
| expected_text_buffer_count += buffer_queue_map_[kTextTrackIdB].size(); |
| if (buffer_queue_map_[kTextTrackIdB].empty()) |
| buffer_queue_map_.erase(kTextTrackIdB); |
| EXPECT_EQ(expected_text_buffer_count, |
| text_in_merged - original_text_in_merged); |
| } |
| |
| // Verifies that MergeBufferQueues() of the current |buffer_queue_map_| and |
| // |merged_buffers_| returns false. |
| void VerifyMergeFailure() { |
| EXPECT_FALSE(MergeBufferQueues(buffer_queue_map_, &merged_buffers_)); |
| } |
| |
| // Helper to allow tests to clear all the input BufferQueues (except |
| // |merged_buffers_|) and the BufferQueueMap that are used in |
| // VerifyMerge{Success/Failure}(). |
| void ClearBufferQueuesButKeepAnyMergedBuffers() { buffer_queue_map_.clear(); } |
| |
| private: |
| StreamParser::BufferQueueMap buffer_queue_map_; |
| BufferQueue merged_buffers_; |
| |
| DISALLOW_COPY_AND_ASSIGN(StreamParserTest); |
| }; |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_AllEmpty) { |
| std::string expected = ""; |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_SingleAudioBuffer) { |
| std::string expected = "A0:100"; |
| int audio_timestamps[] = { 100, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_SingleVideoBuffer) { |
| std::string expected = "V1:100"; |
| int video_timestamps[] = { 100, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_SingleTextBuffer) { |
| std::string expected = "T2:100"; |
| int text_timestamps[] = { 100, kEnd }; |
| GenerateTextBuffers(text_timestamps, NULL); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_OverlappingAudioVideo) { |
| std::string expected = "A0:100 V1:101 V1:102 A0:103 A0:104 V1:105"; |
| int audio_timestamps[] = { 100, 103, 104, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 101, 102, 105, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_OverlappingMultipleText) { |
| std::string expected = "T2:100 T2:101 T3:103 T2:104 T3:105 T3:106"; |
| int text_timestamps_a[] = { 100, 101, 104, kEnd }; |
| int text_timestamps_b[] = { 103, 105, 106, kEnd }; |
| GenerateTextBuffers(text_timestamps_a, text_timestamps_b); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_OverlappingAudioVideoText) { |
| std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 " |
| "T2:107"; |
| int audio_timestamps[] = { 100, 105, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 101, 103, 106, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| int text_timestamps_a[] = { 102, 107, kEnd }; |
| int text_timestamps_b[] = { 104, kEnd }; |
| GenerateTextBuffers(text_timestamps_a, text_timestamps_b); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_NonDecreasingNoCrossMediaDuplicate) { |
| std::string expected = "A0:100 A0:100 A0:100 V1:101 V1:101 V1:101 A0:102 " |
| "V1:103 V1:103"; |
| int audio_timestamps[] = { 100, 100, 100, 102, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 101, 101, 101, 103, 103, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_CrossStreamDuplicates) { |
| // Interface keeps the choice undefined of which stream's buffer wins the |
| // selection when timestamps are tied. Verify at least the right number of |
| // each kind of buffer results, and that buffers are in nondecreasing order. |
| std::string expected = "100 100 100 100 100 100 102 102 102 102 102 102 102"; |
| int audio_timestamps[] = { 100, 100, 100, 102, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 100, 100, 102, 102, 102, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| int text_timestamps[] = { 100, 102, 102, 102, kEnd }; |
| GenerateTextBuffers(text_timestamps, NULL); |
| VerifyMergeSuccess(expected, false); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_InvalidDecreasingSingleStream) { |
| int audio_timestamps[] = { 101, 102, 100, 103, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| VerifyMergeFailure(); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_InvalidDecreasingMultipleStreams) { |
| int audio_timestamps[] = { 101, 102, 100, 103, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 104, 100, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| VerifyMergeFailure(); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_ValidAppendToExistingMerge) { |
| std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 " |
| "T2:107"; |
| int audio_timestamps[] = { 100, 105, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 101, 103, 106, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| int text_timestamps_a[] = { 102, 107, kEnd }; |
| int text_timestamps_b[] = { 104, kEnd }; |
| GenerateTextBuffers(text_timestamps_a, text_timestamps_b); |
| VerifyMergeSuccess(expected, true); |
| |
| ClearBufferQueuesButKeepAnyMergedBuffers(); |
| |
| expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 T2:107 " |
| "A0:107 V1:111 T2:112 V1:113 T3:114 A0:115 V1:116 T2:117"; |
| int more_audio_timestamps[] = { 107, 115, kEnd }; |
| GenerateAudioBuffers(more_audio_timestamps); |
| int more_video_timestamps[] = { 111, 113, 116, kEnd }; |
| GenerateVideoBuffers(more_video_timestamps); |
| int more_text_timestamps_a[] = { 112, 117, kEnd }; |
| int more_text_timestamps_b[] = { 114, kEnd }; |
| GenerateTextBuffers(more_text_timestamps_a, more_text_timestamps_b); |
| VerifyMergeSuccess(expected, true); |
| } |
| |
| TEST_F(StreamParserTest, MergeBufferQueues_InvalidAppendToExistingMerge) { |
| std::string expected = "A0:100 V1:101 T2:102 V1:103 T3:104 A0:105 V1:106 " |
| "T2:107"; |
| int audio_timestamps[] = { 100, 105, kEnd }; |
| GenerateAudioBuffers(audio_timestamps); |
| int video_timestamps[] = { 101, 103, 106, kEnd }; |
| GenerateVideoBuffers(video_timestamps); |
| int text_timestamps_a[] = { 102, 107, kEnd }; |
| int text_timestamps_b[] = { 104, kEnd }; |
| GenerateTextBuffers(text_timestamps_a, text_timestamps_b); |
| VerifyMergeSuccess(expected, true); |
| |
| // Appending empty buffers to pre-existing merge result should succeed and not |
| // change the existing result. |
| ClearBufferQueuesButKeepAnyMergedBuffers(); |
| VerifyMergeSuccess(expected, true); |
| |
| // But appending something with a lower timestamp than the last timestamp |
| // in the pre-existing merge result should fail. |
| int more_audio_timestamps[] = { 106, kEnd }; |
| GenerateAudioBuffers(more_audio_timestamps); |
| VerifyMergeFailure(); |
| } |
| |
| } // namespace media |