#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "media/filters/ffmpeg_demuxer.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "media/base/decrypt_config.h"
#include "media/base/demuxer_stream.h"
#include "media/base/media_switches.h"
#include "media/base/media_tracks.h"
#include "media/base/media_util.h"
#include "media/base/mock_demuxer_host.h"
#include "media/base/mock_media_log.h"
#include "media/base/supported_types.h"
#include "media/base/test_helpers.h"
#include "media/base/timestamp_constants.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/filters/file_data_source.h"
#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/bitstream_converter.h"
#include "media/media_buildflags.h"
#include "media/mojo/services/gpu_mojo_media_client_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/color_space.h"
_;
AnyNumber;
DoAll;
Exactly;
InSequence;
Invoke;
NotNull;
Return;
SaveArg;
SetArgPointee;
StrictMock;
WithArgs;
namespace media {
MATCHER_P(SimpleCreatedFFmpegDemuxerStream, stream_type, "") { … }
MATCHER_P(FailedToCreateValidDecoderConfigFromStream, stream_type, "") { … }
MATCHER_P(SkippingUnsupportedStream, stream_type, "") { … }
const uint8_t kEncryptedMediaInitData[] = …;
static void EosOnReadDone(bool* got_eos_buffer,
base::OnceClosure quit_closure,
DemuxerStream::Status status,
DemuxerStream::DecoderBufferVector buffers) { … }
class FFmpegDemuxerTest : public testing::Test { … };
TEST_F(FFmpegDemuxerTest, Initialize_OpenFails) { … }
TEST_F(FFmpegDemuxerTest, Initialize_NoStreams) { … }
TEST_F(FFmpegDemuxerTest, Initialize_NoAudioVideo) { … }
TEST_F(FFmpegDemuxerTest, Initialize_Successful) { … }
TEST_F(FFmpegDemuxerTest, Initialize_Multitrack) { … }
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(FFmpegDemuxerTest, Initialize_Multitrack_Disabled) {
CreateDemuxer("multitrack-disabled.mp4");
InitializeDemuxer();
ASSERT_EQ(2u, media_tracks_->tracks().size());
EXPECT_FALSE(media_tracks_->tracks()[0]->enabled());
EXPECT_TRUE(media_tracks_->tracks()[1]->enabled());
}
TEST_F(FFmpegDemuxerTest, Initialize_Track_Disabled) {
CreateDemuxer("track-disabled.mp4");
InitializeDemuxer();
ASSERT_EQ(1u, media_tracks_->tracks().size());
EXPECT_TRUE(media_tracks_->tracks()[0]->enabled());
}
#endif
TEST_F(FFmpegDemuxerTest, Initialize_Encrypted) { … }
TEST_F(FFmpegDemuxerTest, Initialize_NoConfigChangeSupport) { … }
TEST_F(FFmpegDemuxerTest, AbortPendingReads) { … }
TEST_F(FFmpegDemuxerTest, Read_Audio) { … }
TEST_F(FFmpegDemuxerTest, Read_Video) { … }
TEST_F(FFmpegDemuxerTest, SeekInitialized_NoVideoStartTime) { … }
TEST_F(FFmpegDemuxerTest, Seeking_PreferredStreamSelection) { … }
TEST_F(FFmpegDemuxerTest, Read_VideoPositiveStartTime) { … }
TEST_F(FFmpegDemuxerTest, Read_AudioNoStartTime) { … }
TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOggDiscard_Sync) { … }
TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOpusDiscard_Sync) { … }
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(FFmpegDemuxerTest,
Read_AudioNegativeStartTimeAndOpusDiscardH264Mp4_Sync) {
CreateDemuxer("tos-h264-opus.mp4");
InitializeDemuxer();
DemuxerStream* video = GetStream(DemuxerStream::VIDEO);
DemuxerStream* audio = GetStream(DemuxerStream::AUDIO);
EXPECT_EQ(audio->audio_decoder_config().codec_delay(), 312);
static const int kTestExpectations[][2] = {
{234, 20000}, {228, 40000}, {340, 60000}};
for (int i = 0; i < 2; ++i) {
Read(audio, FROM_HERE, 408, 0, true, DemuxerStream::Status::kOk,
base::Microseconds(6500));
for (size_t j = 0; j < std::size(kTestExpectations); ++j) {
Read(audio, FROM_HERE, kTestExpectations[j][0], kTestExpectations[j][1],
true);
}
EXPECT_EQ(base::TimeDelta(), demuxer_->GetStartTime());
Read(video, FROM_HERE, 185105, 0, true);
Read(video, FROM_HERE, 35941, 125000, false);
Read(video, FROM_HERE, 8129, 84000, false);
WaitableMessageLoopEvent event;
demuxer_->Seek(base::TimeDelta(), event.GetPipelineStatusCB());
event.RunAndWaitForStatus(PIPELINE_OK);
}
}
TEST_F(FFmpegDemuxerTest, Read_AudioVideoNegativeStartTime) {
CreateDemuxer("sync2-trimmed.mp4");
InitializeDemuxer();
DemuxerStream* video = GetStream(DemuxerStream::VIDEO);
DemuxerStream* audio = GetStream(DemuxerStream::AUDIO);
Read(audio, FROM_HERE, 10, 0, true, DemuxerStream::Status::kOk,
base::Microseconds(1005464));
Read(audio, FROM_HERE, 10, 23220, true, DemuxerStream::Status::kOk,
kInfiniteDuration);
base::RunLoop run_loop;
audio->Read(41, base::BindLambdaForTesting(
[&](DemuxerStream::Status status,
DemuxerStream::DecoderBufferVector buffers) {
for (const auto& buffer : buffers) {
EXPECT_EQ(buffer->discard_padding().first,
kInfiniteDuration);
}
run_loop.QuitWhenIdle();
}));
run_loop.Run();
task_environment_.RunUntilIdle();
Read(audio, FROM_HERE, 10, 998458, true);
Read(video, FROM_HERE, 26791, -66733, true, DemuxerStream::Status::kOk,
kInfiniteDuration);
Read(video, FROM_HERE, 4233, 33367, false);
Read(video, FROM_HERE, 3257, 0, false);
Read(video, FROM_HERE, 2467, -33367, false, DemuxerStream::Status::kOk,
kInfiniteDuration);
Read(video, FROM_HERE, 4049, 133467, false);
}
#endif
TEST_F(FFmpegDemuxerTest, Read_AudioNegativeStartTimeAndOpusSfxDiscard_Sync) { … }
TEST_F(FFmpegDemuxerTest, Read_DiscardDisabledVideoStream) { … }
TEST_F(FFmpegDemuxerTest, Read_EndOfStream) { … }
TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration) { … }
TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration_VideoOnly) { … }
TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration_AudioOnly) { … }
TEST_F(FFmpegDemuxerTest, Read_EndOfStream_NoDuration_UnsupportedStream) { … }
TEST_F(FFmpegDemuxerTest, Seek) { … }
TEST_F(FFmpegDemuxerTest, CancelledSeek) { … }
TEST_F(FFmpegDemuxerTest, Stop) { … }
TEST_F(FFmpegDemuxerTest, SeekWithCuesBeforeFirstCluster) { … }
TEST_F(FFmpegDemuxerTest, NoID3TagData) { … }
TEST_F(FFmpegDemuxerTest, Mp3WithVideoStreamID3TagData) { … }
TEST_F(FFmpegDemuxerTest, UnsupportedAudioSupportedVideoDemux) { … }
TEST_F(FFmpegDemuxerTest, UnsupportedVideoSupportedAudioDemux) { … }
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(FFmpegDemuxerTest, MP4_ZeroStszEntry) {
CreateDemuxer("bear-1280x720-zero-stsz-entry.mp4");
InitializeDemuxer();
ReadUntilEndOfStream(GetStream(DemuxerStream::AUDIO));
}
#endif
class Mp3SeekFFmpegDemuxerTest
: public FFmpegDemuxerTest,
public testing::WithParamInterface<const char*> { … };
TEST_P(Mp3SeekFFmpegDemuxerTest, TestFastSeek) { … }
INSTANTIATE_TEST_SUITE_P(…);
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
static void ValidateAnnexB(DemuxerStream* stream,
base::OnceClosure quit_closure,
DemuxerStream::Status status,
DemuxerStream::DecoderBufferVector buffers) {
EXPECT_EQ(status, DemuxerStream::kOk);
EXPECT_EQ(buffers.size(), 1u);
scoped_refptr<DecoderBuffer> buffer = std::move(buffers[0]);
if (buffer->end_of_stream()) {
std::move(quit_closure).Run();
return;
}
std::vector<SubsampleEntry> subsamples;
if (buffer->decrypt_config())
subsamples = buffer->decrypt_config()->subsamples();
bool is_valid =
mp4::AVC::AnalyzeAnnexB(buffer->data(), buffer->size(), subsamples)
.is_conformant.value_or(false);
EXPECT_TRUE(is_valid);
if (!is_valid) {
LOG(ERROR) << "Buffer contains invalid Annex B data.";
std::move(quit_closure).Run();
return;
}
stream->Read(
1, base::BindOnce(&ValidateAnnexB, stream, std::move(quit_closure)));
}
TEST_F(FFmpegDemuxerTest, IsValidAnnexB) {
const char* files[] = {"bear-1280x720-av_frag.mp4",
"bear-1280x720-av_with-aud-nalus_frag.mp4"};
for (size_t i = 0; i < std::size(files); ++i) {
DVLOG(1) << "Testing " << files[i];
CreateDemuxer(files[i]);
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
stream->EnableBitstreamConverter();
base::RunLoop loop;
stream->Read(
1, base::BindOnce(&ValidateAnnexB, stream, loop.QuitWhenIdleClosure()));
loop.Run();
demuxer_->Stop();
demuxer_.reset();
data_source_.reset();
}
}
TEST_F(FFmpegDemuxerTest, Rotate_Metadata_0) {
CreateDemuxer("bear_rotate_0.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
ASSERT_EQ(VIDEO_ROTATION_0, video_config.video_transformation().rotation);
}
TEST_F(FFmpegDemuxerTest, Rotate_Metadata_90) {
CreateDemuxer("bear_rotate_90.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
ASSERT_EQ(VIDEO_ROTATION_90, video_config.video_transformation().rotation);
}
TEST_F(FFmpegDemuxerTest, Rotate_Metadata_180) {
CreateDemuxer("bear_rotate_180.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
ASSERT_EQ(VIDEO_ROTATION_180, video_config.video_transformation().rotation);
}
TEST_F(FFmpegDemuxerTest, Rotate_Metadata_270) {
CreateDemuxer("bear_rotate_270.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
ASSERT_EQ(VIDEO_ROTATION_270, video_config.video_transformation().rotation);
}
TEST_F(FFmpegDemuxerTest, NaturalSizeWithoutPASP) {
CreateDemuxer("bear-640x360-non_square_pixel-without_pasp.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
EXPECT_EQ(gfx::Size(639, 360), video_config.natural_size());
}
TEST_F(FFmpegDemuxerTest, NaturalSizeWithPASP) {
CreateDemuxer("bear-640x360-non_square_pixel-with_pasp.mp4");
InitializeDemuxer();
DemuxerStream* stream = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(stream);
const VideoDecoderConfig& video_config = stream->video_decoder_config();
EXPECT_EQ(gfx::Size(639, 360), video_config.natural_size());
}
TEST_F(FFmpegDemuxerTest, HEVC_in_MP4_container) {
CreateDemuxer("bear-hevc-frag.mp4");
const VideoType kHevc{
.codec = VideoCodec::kHEVC,
.profile = HEVCPROFILE_MIN,
.color_space = VideoColorSpace::REC709(),
};
if (IsSupportedVideoType(kHevc)) {
InitializeDemuxer();
DemuxerStream* video = GetStream(DemuxerStream::VIDEO);
ASSERT_TRUE(video);
Read(video, FROM_HERE, 3569, 66733, true);
Read(video, FROM_HERE, 1042, 200200, false);
} else {
InitializeDemuxerAndExpectPipelineStatus(
DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
}
}
TEST_F(FFmpegDemuxerTest, Read_AC3_Audio) {
CreateDemuxer("bear-ac3-only-frag.mp4");
constexpr AudioType kAc3{
.codec = AudioCodec::kAC3,
.spatial_rendering = false,
};
if (IsSupportedAudioType(kAc3)) {
InitializeDemuxer();
DemuxerStream* audio = GetStream(DemuxerStream::AUDIO);
Read(audio, FROM_HERE, 834, 0, true);
Read(audio, FROM_HERE, 836, 34830, true);
} else {
InitializeDemuxerAndExpectPipelineStatus(
DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
}
}
TEST_F(FFmpegDemuxerTest, Read_EAC3_Audio) {
CreateDemuxer("bear-eac3-only-frag.mp4");
constexpr AudioType kEac3{
.codec = AudioCodec::kEAC3,
.spatial_rendering = false,
};
if (IsSupportedAudioType(kEac3)) {
InitializeDemuxer();
DemuxerStream* audio = GetStream(DemuxerStream::AUDIO);
Read(audio, FROM_HERE, 870, 0, true);
Read(audio, FROM_HERE, 872, 34830, true);
} else {
InitializeDemuxerAndExpectPipelineStatus(
DEMUXER_ERROR_NO_SUPPORTED_STREAMS);
}
}
TEST_F(FFmpegDemuxerTest, Read_Mp4_Media_Track_Info) {
CreateDemuxer("bear.mp4");
InitializeDemuxer();
EXPECT_EQ(media_tracks_->tracks().size(), 2u);
const MediaTrack& audio_track = *(media_tracks_->tracks()[1]);
EXPECT_EQ(audio_track.type(), MediaTrack::Type::kAudio);
EXPECT_EQ(audio_track.bytestream_track_id(), 2);
EXPECT_EQ(audio_track.kind().value(), "main");
EXPECT_EQ(audio_track.label().value(), "SoundHandler");
EXPECT_EQ(audio_track.language().value(), "und");
const MediaTrack& video_track = *(media_tracks_->tracks()[0]);
EXPECT_EQ(video_track.type(), MediaTrack::Type::kVideo);
EXPECT_EQ(video_track.bytestream_track_id(), 1);
EXPECT_EQ(video_track.kind().value(), "main");
EXPECT_EQ(video_track.label().value(), "VideoHandler");
EXPECT_EQ(video_track.language().value(), "und");
}
TEST_F(FFmpegDemuxerTest, Read_Mp4_Multiple_Tracks) {
CreateDemuxer("bbb-320x240-2video-2audio.mp4");
InitializeDemuxer();
EXPECT_EQ(media_tracks_->tracks().size(), 4u);
const MediaTrack& video_track = *(media_tracks_->tracks()[0]);
EXPECT_EQ(video_track.type(), MediaTrack::Type::kVideo);
EXPECT_EQ(video_track.bytestream_track_id(), 1);
EXPECT_EQ(video_track.kind().value(), "main");
EXPECT_EQ(video_track.label().value(), "VideoHandler");
EXPECT_EQ(video_track.language().value(), "und");
const MediaTrack& audio_track = *(media_tracks_->tracks()[1]);
EXPECT_EQ(audio_track.type(), MediaTrack::Type::kAudio);
EXPECT_EQ(audio_track.bytestream_track_id(), 2);
EXPECT_EQ(audio_track.kind().value(), "main");
EXPECT_EQ(audio_track.label().value(), "SoundHandler");
EXPECT_EQ(audio_track.language().value(), "und");
const MediaTrack& video_track2 = *(media_tracks_->tracks()[2]);
EXPECT_EQ(video_track2.type(), MediaTrack::Type::kVideo);
EXPECT_EQ(video_track2.bytestream_track_id(), 3);
EXPECT_EQ(video_track2.kind().value(), "main");
EXPECT_EQ(video_track2.label().value(), "VideoHandler");
EXPECT_EQ(video_track2.language().value(), "und");
const MediaTrack& audio_track2 = *(media_tracks_->tracks()[3]);
EXPECT_EQ(audio_track2.type(), MediaTrack::Type::kAudio);
EXPECT_EQ(audio_track2.bytestream_track_id(), 4);
EXPECT_EQ(audio_track2.kind().value(), "main");
EXPECT_EQ(audio_track2.label().value(), "SoundHandler");
EXPECT_EQ(audio_track2.language().value(), "und");
}
TEST_F(FFmpegDemuxerTest, Read_Mp4_Crbug657437) {
CreateDemuxer("crbug657437.mp4");
InitializeDemuxer();
}
TEST_F(FFmpegDemuxerTest, XHE_AAC) {
if (!IsSupportedAudioType(
{AudioCodec::kAAC, AudioCodecProfile::kXHE_AAC, false})) {
GTEST_SKIP() << "Unsupported platform.";
}
CreateDemuxer("noise-xhe-aac.mp4");
InitializeDemuxer();
DemuxerStream* audio = GetStream(DemuxerStream::AUDIO);
ASSERT_TRUE(audio);
EXPECT_EQ(audio->audio_decoder_config().profile(),
AudioCodecProfile::kXHE_AAC);
audio->EnableBitstreamConverter();
EXPECT_FALSE(HasBitstreamConverter(audio));
Read(audio, FROM_HERE, 1796, 0, true);
}
#endif
TEST_F(FFmpegDemuxerTest, Read_Webm_Multiple_Tracks) { … }
TEST_F(FFmpegDemuxerTest, Read_Webm_Media_Track_Info) { … }
TEST_F(FFmpegDemuxerTest, UTCDateToTime_Valid) { … }
TEST_F(FFmpegDemuxerTest, UTCDateToTime_Invalid) { … }
static void VerifyFlacStream(DemuxerStream* stream,
int expected_bytes_per_channel,
ChannelLayout expected_channel_layout,
int expected_samples_per_second,
SampleFormat expected_sample_format) { … }
TEST_F(FFmpegDemuxerTest, Read_Flac) { … }
TEST_F(FFmpegDemuxerTest, Read_Flac_Mp4) { … }
TEST_F(FFmpegDemuxerTest, Read_Flac_192kHz_Mp4) { … }
TEST_F(FFmpegDemuxerTest, Seek_FallbackToDisabledVideoStream) { … }
TEST_F(FFmpegDemuxerTest, Seek_FallbackToDisabledAudioStream) { … }
namespace {
void QuitLoop(base::OnceClosure quit_closure,
DemuxerStream::Type type,
const std::vector<DemuxerStream*>& streams) { … }
void DisableAndEnableDemuxerTracks(
FFmpegDemuxer* demuxer,
base::test::TaskEnvironment* task_environment) { … }
void OnReadDoneExpectEos(DemuxerStream::Status status,
DemuxerStream::DecoderBufferVector buffers) { … }
}
TEST_F(FFmpegDemuxerTest, StreamStatusNotifications) { … }
TEST_F(FFmpegDemuxerTest, MultitrackMemoryUsage) { … }
TEST_F(FFmpegDemuxerTest, SeekOnVideoTrackChangeWontSeekIfEmpty) { … }
}