FuzzMedia.cpp (13697B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "ADTSDemuxer.h" 7 #include "BufferMediaResource.h" 8 #include "FlacDemuxer.h" 9 #include "FuzzingInterface.h" 10 #include "MP3Demuxer.h" 11 #include "MP4Demuxer.h" 12 #include "MediaDataDecoderProxy.h" 13 #include "OggDemuxer.h" 14 #include "PDMFactory.h" 15 #include "PlatformDecoderModule.h" 16 #include "QueueObject.h" 17 #include "WaveDemuxer.h" 18 #include "WebMDemuxer.h" 19 #include "mozilla/AbstractThread.h" 20 #include "mozilla/SpinEventLoopUntil.h" 21 #include "mozilla/TaskQueue.h" 22 #include "mozilla/gfx/gfxVars.h" 23 #include "systemservices/MediaUtils.h" 24 25 using namespace mozilla; 26 27 class Benchmark; 28 29 class BenchmarkPlayback : public QueueObject { 30 friend class Benchmark; 31 BenchmarkPlayback(Benchmark* aGlobalState, MediaDataDemuxer* aDemuxer); 32 void DemuxSamples(); 33 void DemuxNextSample(); 34 void GlobalShutdown(); 35 void InitDecoder(UniquePtr<TrackInfo>&& aInfo); 36 37 void Output(MediaDataDecoder::DecodedData&& aResults); 38 void Error(const MediaResult& aError); 39 void InputExhausted(); 40 41 // Shutdown trackdemuxer and demuxer if any and shutdown the task queues. 42 void FinalizeShutdown(); 43 44 Atomic<Benchmark*> mGlobalState; 45 46 RefPtr<TaskQueue> mDecoderTaskQueue; 47 RefPtr<MediaDataDecoder> mDecoder; 48 49 // Object only accessed on Thread() 50 RefPtr<MediaDataDemuxer> mDemuxer; 51 RefPtr<MediaTrackDemuxer> mTrackDemuxer; 52 nsTArray<RefPtr<MediaRawData>> mSamples; 53 UniquePtr<TrackInfo> mInfo; 54 size_t mSampleIndex; 55 Maybe<TimeStamp> mDecodeStartTime; 56 uint32_t mFrameCount; 57 bool mFinished; 58 bool mDrained; 59 }; 60 61 // Init() must have been called at least once prior on the 62 // main thread. 63 class Benchmark : public QueueObject { 64 public: 65 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Benchmark) 66 67 struct Parameters { 68 Parameters() 69 : mFramesToMeasure(UINT32_MAX), 70 mStartupFrame(1), 71 mTimeout(TimeDuration::Forever()) {} 72 73 Parameters(uint32_t aFramesToMeasure, uint32_t aStartupFrame, 74 uint32_t aStopAtFrame, const TimeDuration& aTimeout) 75 : mFramesToMeasure(aFramesToMeasure), 76 mStartupFrame(aStartupFrame), 77 mStopAtFrame(Some(aStopAtFrame)), 78 mTimeout(aTimeout) {} 79 80 const uint32_t mFramesToMeasure; 81 const uint32_t mStartupFrame; 82 const Maybe<uint32_t> mStopAtFrame; 83 const TimeDuration mTimeout; 84 }; 85 86 typedef MozPromise<uint32_t, MediaResult, /* IsExclusive = */ true> 87 BenchmarkPromise; 88 89 explicit Benchmark(MediaDataDemuxer* aDemuxer, 90 const Parameters& aParameters = Parameters()); 91 RefPtr<BenchmarkPromise> Run(); 92 93 // Must be called on the main thread. 94 static void Init(); 95 96 private: 97 friend class BenchmarkPlayback; 98 virtual ~Benchmark(); 99 void ReturnResult(uint32_t aDecodeFps); 100 void ReturnError(const MediaResult& aError); 101 void Dispose(); 102 const Parameters mParameters; 103 RefPtr<Benchmark> mKeepAliveUntilComplete; 104 BenchmarkPlayback mPlaybackState; 105 MozPromiseHolder<BenchmarkPromise> mPromise; 106 }; 107 108 class FuzzRunner { 109 public: 110 explicit FuzzRunner(Benchmark* aBenchmark) : mBenchmark(aBenchmark) {} 111 112 void Run() { 113 // Assert we're on the main thread, otherwise `done` must be synchronized. 114 MOZ_ASSERT(NS_IsMainThread()); 115 bool done = false; 116 117 Benchmark::Init(); 118 mBenchmark->Run()->Then( 119 // Non DocGroup-version of AbstractThread::MainThread() is fine for 120 // testing. 121 AbstractThread::MainThread(), __func__, 122 [&](uint32_t aDecodeFps) { done = true; }, [&]() { done = true; }); 123 124 // Wait until benchmark completes. 125 SpinEventLoopUntil("FuzzRunner::Run"_ns, [&]() { return done; }); 126 } 127 128 private: 129 RefPtr<Benchmark> mBenchmark; 130 }; 131 132 Benchmark::Benchmark(MediaDataDemuxer* aDemuxer, const Parameters& aParameters) 133 : QueueObject( 134 TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), 135 "Benchmark::QueueObject")), 136 mParameters(aParameters), 137 mPlaybackState(this, aDemuxer) { 138 MOZ_COUNT_CTOR(Benchmark); 139 } 140 141 Benchmark::~Benchmark() { MOZ_COUNT_DTOR(Benchmark); } 142 143 RefPtr<Benchmark::BenchmarkPromise> Benchmark::Run() { 144 RefPtr<Benchmark> self = this; 145 mKeepAliveUntilComplete = this; 146 return InvokeAsync(Thread(), __func__, [self] { 147 RefPtr<BenchmarkPromise> p = self->mPromise.Ensure(__func__); 148 self->mPlaybackState.Dispatch(NS_NewRunnableFunction( 149 "Benchmark::Run", [self]() { self->mPlaybackState.DemuxSamples(); })); 150 return p; 151 }); 152 } 153 154 void Benchmark::ReturnResult(uint32_t aDecodeFps) { 155 MOZ_ASSERT(OnThread()); 156 157 mPromise.ResolveIfExists(aDecodeFps, __func__); 158 } 159 160 void Benchmark::ReturnError(const MediaResult& aError) { 161 MOZ_ASSERT(OnThread()); 162 163 mPromise.RejectIfExists(aError, __func__); 164 } 165 166 void Benchmark::Dispose() { 167 MOZ_ASSERT(OnThread()); 168 169 mKeepAliveUntilComplete = nullptr; 170 } 171 172 void Benchmark::Init() { 173 MOZ_ASSERT(NS_IsMainThread()); 174 mozilla::gfx::gfxVars::Initialize(); 175 } 176 177 BenchmarkPlayback::BenchmarkPlayback(Benchmark* aGlobalState, 178 MediaDataDemuxer* aDemuxer) 179 : QueueObject( 180 TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), 181 "BenchmarkPlayback::QueueObject")), 182 mGlobalState(aGlobalState), 183 mDecoderTaskQueue(TaskQueue::Create( 184 GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER), 185 "BenchmarkPlayback::mDecoderTaskQueue")), 186 mDemuxer(aDemuxer), 187 mSampleIndex(0), 188 mFrameCount(0), 189 mFinished(false), 190 mDrained(false) {} 191 192 void BenchmarkPlayback::DemuxSamples() { 193 MOZ_ASSERT(OnThread()); 194 195 RefPtr<Benchmark> ref(mGlobalState); 196 mDemuxer->Init()->Then( 197 Thread(), __func__, 198 [this, ref](nsresult aResult) { 199 MOZ_ASSERT(OnThread()); 200 if (mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack)) { 201 mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0); 202 } else if (mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack)) { 203 mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0); 204 } 205 if (!mTrackDemuxer) { 206 Error(MediaResult(NS_ERROR_FAILURE, "Can't create track demuxer")); 207 return; 208 } 209 DemuxNextSample(); 210 }, 211 [this, ref](const MediaResult& aError) { Error(aError); }); 212 } 213 214 void BenchmarkPlayback::DemuxNextSample() { 215 MOZ_ASSERT(OnThread()); 216 217 RefPtr<Benchmark> ref(mGlobalState); 218 RefPtr<MediaTrackDemuxer::SamplesPromise> promise = 219 mTrackDemuxer->GetSamples(); 220 promise->Then( 221 Thread(), __func__, 222 [this, ref](RefPtr<MediaTrackDemuxer::SamplesHolder> aHolder) { 223 mSamples.AppendElements(aHolder->GetMovableSamples()); 224 if (ref->mParameters.mStopAtFrame && 225 mSamples.Length() == ref->mParameters.mStopAtFrame.ref()) { 226 InitDecoder(mTrackDemuxer->GetInfo()); 227 } else { 228 Dispatch( 229 NS_NewRunnableFunction("BenchmarkPlayback::DemuxNextSample", 230 [this, ref]() { DemuxNextSample(); })); 231 } 232 }, 233 [this, ref](const MediaResult& aError) { 234 switch (aError.Code()) { 235 case NS_ERROR_DOM_MEDIA_END_OF_STREAM: 236 InitDecoder(mTrackDemuxer->GetInfo()); 237 break; 238 default: 239 Error(aError); 240 break; 241 } 242 }); 243 } 244 245 void BenchmarkPlayback::InitDecoder(UniquePtr<TrackInfo>&& aInfo) { 246 MOZ_ASSERT(OnThread()); 247 248 if (!aInfo) { 249 Error(MediaResult(NS_ERROR_FAILURE, "Invalid TrackInfo")); 250 return; 251 } 252 253 RefPtr<PDMFactory> platform = new PDMFactory(); 254 mInfo = std::move(aInfo); 255 RefPtr<Benchmark> ref(mGlobalState); 256 platform->CreateDecoder(CreateDecoderParams{*mInfo}) 257 ->Then( 258 Thread(), __func__, 259 [this, ref](RefPtr<MediaDataDecoder>&& aDecoder) { 260 mDecoder = new MediaDataDecoderProxy( 261 aDecoder.forget(), do_AddRef(mDecoderTaskQueue.get())); 262 mDecoder->Init()->Then( 263 Thread(), __func__, 264 [this, ref](TrackInfo::TrackType aTrackType) { 265 InputExhausted(); 266 }, 267 [this, ref](const MediaResult& aError) { Error(aError); }); 268 }, 269 [this, ref](const MediaResult& aError) { Error(aError); }); 270 } 271 272 void BenchmarkPlayback::FinalizeShutdown() { 273 MOZ_ASSERT(OnThread()); 274 275 MOZ_ASSERT(mFinished, "GlobalShutdown must have been run"); 276 MOZ_ASSERT(!mDecoder, "mDecoder must have been shutdown already"); 277 MOZ_ASSERT(!mDemuxer, "mDemuxer must have been shutdown already"); 278 MOZ_DIAGNOSTIC_ASSERT(mDecoderTaskQueue->IsEmpty()); 279 mDecoderTaskQueue = nullptr; 280 281 RefPtr<Benchmark> ref(mGlobalState); 282 ref->Thread()->Dispatch(NS_NewRunnableFunction( 283 "BenchmarkPlayback::FinalizeShutdown", [ref]() { ref->Dispose(); })); 284 } 285 286 void BenchmarkPlayback::GlobalShutdown() { 287 MOZ_ASSERT(OnThread()); 288 289 MOZ_ASSERT(!mFinished, "We've already shutdown"); 290 291 mFinished = true; 292 293 if (mTrackDemuxer) { 294 mTrackDemuxer->Reset(); 295 mTrackDemuxer->BreakCycles(); 296 mTrackDemuxer = nullptr; 297 } 298 mDemuxer = nullptr; 299 300 if (mDecoder) { 301 RefPtr<Benchmark> ref(mGlobalState); 302 mDecoder->Flush()->Then( 303 Thread(), __func__, 304 [ref, this]() { 305 mDecoder->Shutdown()->Then( 306 Thread(), __func__, [ref, this]() { FinalizeShutdown(); }, 307 []() { MOZ_CRASH("not reached"); }); 308 mDecoder = nullptr; 309 mInfo = nullptr; 310 }, 311 []() { MOZ_CRASH("not reached"); }); 312 } else { 313 FinalizeShutdown(); 314 } 315 } 316 317 void BenchmarkPlayback::Output(MediaDataDecoder::DecodedData&& aResults) { 318 MOZ_ASSERT(OnThread()); 319 MOZ_ASSERT(!mFinished); 320 321 RefPtr<Benchmark> ref(mGlobalState); 322 mFrameCount += aResults.Length(); 323 if (!mDecodeStartTime && mFrameCount >= ref->mParameters.mStartupFrame) { 324 mDecodeStartTime = Some(TimeStamp::Now()); 325 } 326 TimeStamp now = TimeStamp::Now(); 327 uint32_t frames = mFrameCount - ref->mParameters.mStartupFrame; 328 TimeDuration elapsedTime = now - mDecodeStartTime.refOr(now); 329 if (((frames == ref->mParameters.mFramesToMeasure) && 330 mFrameCount > ref->mParameters.mStartupFrame && frames > 0) || 331 elapsedTime >= ref->mParameters.mTimeout || mDrained) { 332 uint32_t decodeFps = frames / elapsedTime.ToSeconds(); 333 GlobalShutdown(); 334 ref->Dispatch(NS_NewRunnableFunction( 335 "BenchmarkPlayback::Output", 336 [ref, decodeFps]() { ref->ReturnResult(decodeFps); })); 337 } 338 } 339 340 void BenchmarkPlayback::Error(const MediaResult& aError) { 341 MOZ_ASSERT(OnThread()); 342 343 RefPtr<Benchmark> ref(mGlobalState); 344 GlobalShutdown(); 345 ref->Dispatch( 346 NS_NewRunnableFunction("BenchmarkPlayback::Error", 347 [ref, aError]() { ref->ReturnError(aError); })); 348 } 349 350 void BenchmarkPlayback::InputExhausted() { 351 MOZ_ASSERT(OnThread()); 352 MOZ_ASSERT(!mFinished); 353 354 if (mSampleIndex >= mSamples.Length()) { 355 Error(MediaResult(NS_ERROR_FAILURE, "Nothing left to decode")); 356 return; 357 } 358 359 RefPtr<MediaRawData> sample = mSamples[mSampleIndex]; 360 RefPtr<Benchmark> ref(mGlobalState); 361 RefPtr<MediaDataDecoder::DecodePromise> p = mDecoder->Decode(sample); 362 363 mSampleIndex++; 364 if (mSampleIndex == mSamples.Length() && !ref->mParameters.mStopAtFrame) { 365 // Complete current frame decode then drain if still necessary. 366 p->Then( 367 Thread(), __func__, 368 [ref, this](MediaDataDecoder::DecodedData&& aResults) { 369 Output(std::move(aResults)); 370 if (!mFinished) { 371 mDecoder->Drain()->Then( 372 Thread(), __func__, 373 [ref, this](MediaDataDecoder::DecodedData&& aResults) { 374 mDrained = true; 375 Output(std::move(aResults)); 376 MOZ_ASSERT(mFinished, "We must be done now"); 377 }, 378 [ref, this](const MediaResult& aError) { Error(aError); }); 379 } 380 }, 381 [ref, this](const MediaResult& aError) { Error(aError); }); 382 } else { 383 if (mSampleIndex == mSamples.Length() && ref->mParameters.mStopAtFrame) { 384 mSampleIndex = 0; 385 } 386 // Continue decoding 387 p->Then( 388 Thread(), __func__, 389 [ref, this](MediaDataDecoder::DecodedData&& aResults) { 390 Output(std::move(aResults)); 391 if (!mFinished) { 392 InputExhausted(); 393 } 394 }, 395 [ref, this](const MediaResult& aError) { Error(aError); }); 396 } 397 } 398 399 #define MOZ_MEDIA_FUZZER(_name) \ 400 static int FuzzingRunMedia##_name(const uint8_t* data, size_t size) { \ 401 if (!size) { \ 402 return 0; \ 403 } \ 404 RefPtr<BufferMediaResource> resource = \ 405 new BufferMediaResource(data, size); \ 406 FuzzRunner runner(new Benchmark(new _name##Demuxer(resource))); \ 407 runner.Run(); \ 408 return 0; \ 409 } \ 410 MOZ_FUZZING_INTERFACE_RAW(nullptr, FuzzingRunMedia##_name, Media##_name); 411 412 MOZ_MEDIA_FUZZER(ADTS); 413 MOZ_MEDIA_FUZZER(Flac); 414 MOZ_MEDIA_FUZZER(MP3); 415 MOZ_MEDIA_FUZZER(MP4); 416 MOZ_MEDIA_FUZZER(Ogg); 417 MOZ_MEDIA_FUZZER(WAV); 418 MOZ_MEDIA_FUZZER(WebM);