Bug 1192675: P1. Ensure VDA/VT APIs are only ever accessed from the same thread. r=cpearce

This commit is contained in:
Jean-Yves Avenard 2015-08-10 18:59:59 +10:00
parent 3a39ae3f5f
commit 22bbf0d187
5 changed files with 206 additions and 60 deletions

View File

@ -43,6 +43,56 @@ private:
T mRef;
};
// CFRefPtr: A CoreFoundation smart pointer.
template <class T>
class CFRefPtr {
public:
explicit CFRefPtr(T aRef)
: mRef(aRef)
{
if (mRef) {
CFRetain(mRef);
}
}
// Copy constructor.
CFRefPtr(const CFRefPtr<T>& aCFRefPtr)
: mRef(aCFRefPtr.mRef)
{
if (mRef) {
CFRetain(mRef);
}
}
// Copy operator
CFRefPtr<T>& operator=(const CFRefPtr<T>& aCFRefPtr)
{
if (mRef == aCFRefPtr.mRef) {
return;
}
if (mRef) {
CFRelease(mRef);
}
mRef = aCFRefPtr.mRef;
if (mRef) {
CFRetain(mRef);
}
return *this;
}
~CFRefPtr()
{
if (mRef) {
CFRelease(mRef);
}
}
// Return the wrapped ref so it can be used as an in parameter.
operator T()
{
return mRef;
}
private:
T mRef;
};
} // namespace mozilla
#endif // mozilla_AppleUtils_h

View File

@ -40,8 +40,12 @@ AppleVDADecoder::AppleVDADecoder(const VideoInfo& aConfig,
, mPictureHeight(aConfig.mImage.height)
, mDisplayWidth(aConfig.mDisplay.width)
, mDisplayHeight(aConfig.mDisplay.height)
, mInputIncoming(0)
, mIsShutDown(false)
, mUseSoftwareImages(false)
, mIs106(!nsCocoaFeatures::OnLionOrLater())
, mMonitor("AppleVideoDecoder")
, mIsFlushing(false)
, mDecoder(nullptr)
{
MOZ_COUNT_CTOR(AppleVDADecoder);
@ -81,18 +85,34 @@ AppleVDADecoder::Init()
nsresult
AppleVDADecoder::Shutdown()
{
MOZ_DIAGNOSTIC_ASSERT(!mIsShutDown);
mIsShutDown = true;
if (mTaskQueue) {
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &AppleVDADecoder::ProcessShutdown);
mTaskQueue->Dispatch(runnable.forget());
} else {
ProcessShutdown();
}
return NS_OK;
}
void
AppleVDADecoder::ProcessShutdown()
{
if (mDecoder) {
LOG("%s: cleaning up decoder %p", __func__, mDecoder);
VDADecoderDestroy(mDecoder);
mDecoder = nullptr;
}
return NS_OK;
}
nsresult
AppleVDADecoder::Input(MediaRawData* aSample)
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
aSample,
aSample->mTime,
@ -100,6 +120,8 @@ AppleVDADecoder::Input(MediaRawData* aSample)
aSample->mKeyframe ? " keyframe" : "",
aSample->Size());
mInputIncoming++;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>(
this,
@ -112,21 +134,51 @@ AppleVDADecoder::Input(MediaRawData* aSample)
nsresult
AppleVDADecoder::Flush()
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
mIsFlushing = true;
mTaskQueue->Flush();
OSStatus rv = VDADecoderFlush(mDecoder, 0 /*dont emit*/);
if (rv != noErr) {
LOG("AppleVDADecoder::Flush failed waiting for platform decoder "
"with error:%d.", rv);
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &AppleVDADecoder::ProcessFlush);
MonitorAutoLock mon(mMonitor);
mTaskQueue->Dispatch(runnable.forget());
while (mIsFlushing) {
mon.Wait();
}
ClearReorderedFrames();
mInputIncoming = 0;
return NS_OK;
}
nsresult
AppleVDADecoder::Drain()
{
mTaskQueue->AwaitIdle();
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethod(this, &AppleVDADecoder::ProcessDrain);
mTaskQueue->Dispatch(runnable.forget());
return NS_OK;
}
void
AppleVDADecoder::ProcessFlush()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
OSStatus rv = VDADecoderFlush(mDecoder, 0 /*dont emit*/);
if (rv != noErr) {
LOG("AppleVDADecoder::Flush failed waiting for platform decoder "
"with error:%d.", rv);
}
ClearReorderedFrames();
MonitorAutoLock mon(mMonitor);
mIsFlushing = false;
mon.NotifyAll();
}
void
AppleVDADecoder::ProcessDrain()
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
OSStatus rv = VDADecoderFlush(mDecoder, kVDADecoderFlush_EmitFrames);
if (rv != noErr) {
LOG("AppleVDADecoder::Drain failed waiting for platform decoder "
@ -134,7 +186,6 @@ AppleVDADecoder::Drain()
}
DrainReorderedFrames();
mCallback->DrainComplete();
return NS_OK;
}
//
@ -199,17 +250,19 @@ PlatformCallback(void* decompressionOutputRefCon,
CFNumberGetValue(boref, kCFNumberSInt64Type, &byte_offset);
CFNumberGetValue(kfref, kCFNumberSInt8Type, &is_sync_point);
nsAutoPtr<AppleVDADecoder::AppleFrameRef> frameRef(
new AppleVDADecoder::AppleFrameRef(
AppleVDADecoder::AppleFrameRef frameRef(
media::TimeUnit::FromMicroseconds(dts),
media::TimeUnit::FromMicroseconds(pts),
media::TimeUnit::FromMicroseconds(duration),
byte_offset,
is_sync_point == 1));
is_sync_point == 1);
// Forward the data back to an object method which can access
// the correct MP4Reader callback.
decoder->OutputFrame(image, frameRef);
// the correct reader's callback.
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<CFRefPtr<CVPixelBufferRef>, AppleVDADecoder::AppleFrameRef>(
decoder, &AppleVDADecoder::OutputFrame, image, frameRef);
decoder->DispatchOutputTask(task.forget());
}
AppleVDADecoder::AppleFrameRef*
@ -237,15 +290,22 @@ AppleVDADecoder::ClearReorderedFrames()
// Copy and return a decoded frame.
nsresult
AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage,
nsAutoPtr<AppleVDADecoder::AppleFrameRef> aFrameRef)
AppleVDADecoder::OutputFrame(CFRefPtr<CVPixelBufferRef> aImage,
AppleVDADecoder::AppleFrameRef aFrameRef)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
if (mIsFlushing) {
// We are in the process of flushing; ignore frame.
return NS_OK;
}
LOG("mp4 output frame %lld dts %lld pts %lld duration %lld us%s",
aFrameRef->byte_offset,
aFrameRef->decode_timestamp.ToMicroseconds(),
aFrameRef->composition_timestamp.ToMicroseconds(),
aFrameRef->duration.ToMicroseconds(),
aFrameRef->is_sync_point ? " keyframe" : ""
aFrameRef.byte_offset,
aFrameRef.decode_timestamp.ToMicroseconds(),
aFrameRef.composition_timestamp.ToMicroseconds(),
aFrameRef.duration.ToMicroseconds(),
aFrameRef.is_sync_point ? " keyframe" : ""
);
// Where our resulting image will end up.
@ -303,12 +363,12 @@ AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage,
VideoData::Create(info,
mImageContainer,
nullptr,
aFrameRef->byte_offset,
aFrameRef->composition_timestamp.ToMicroseconds(),
aFrameRef->duration.ToMicroseconds(),
aFrameRef.byte_offset,
aFrameRef.composition_timestamp.ToMicroseconds(),
aFrameRef.duration.ToMicroseconds(),
buffer,
aFrameRef->is_sync_point,
aFrameRef->decode_timestamp.ToMicroseconds(),
aFrameRef.is_sync_point,
aFrameRef.decode_timestamp.ToMicroseconds(),
visible);
// Unlock the returned image data.
CVPixelBufferUnlockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
@ -327,12 +387,12 @@ AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage,
data =
VideoData::CreateFromImage(info,
mImageContainer,
aFrameRef->byte_offset,
aFrameRef->composition_timestamp.ToMicroseconds(),
aFrameRef->duration.ToMicroseconds(),
aFrameRef.byte_offset,
aFrameRef.composition_timestamp.ToMicroseconds(),
aFrameRef.duration.ToMicroseconds(),
image.forget(),
aFrameRef->is_sync_point,
aFrameRef->decode_timestamp.ToMicroseconds(),
aFrameRef.is_sync_point,
aFrameRef.decode_timestamp.ToMicroseconds(),
visible);
}
@ -357,6 +417,10 @@ AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage,
nsresult
AppleVDADecoder::SubmitFrame(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
mInputIncoming--;
AutoCFRelease<CFDataRef> block =
CFDataCreate(kCFAllocatorDefault, aSample->Data(), aSample->Size());
if (!block) {
@ -430,7 +494,7 @@ AppleVDADecoder::SubmitFrame(MediaRawData* aSample)
}
// Ask for more data.
if (mTaskQueue->IsEmpty()) {
if (!mInputIncoming) {
LOG("AppleVDADecoder task queue empty; requesting more data");
mCallback->InputExhausted();
}

View File

@ -8,6 +8,7 @@
#define mozilla_AppleVDADecoder_h
#include "PlatformDecoderModule.h"
#include "mozilla/Atomics.h"
#include "mozilla/ReentrantMonitor.h"
#include "MP4Decoder.h"
#include "nsIThread.h"
@ -80,18 +81,28 @@ public:
return true;
}
nsresult OutputFrame(CVPixelBufferRef aImage,
nsAutoPtr<AppleFrameRef> aFrameRef);
void DispatchOutputTask(already_AddRefed<nsIRunnable> aTask)
{
nsCOMPtr<nsIRunnable> task = aTask;
if (mIsShutDown || mIsFlushing) {
return;
}
mTaskQueue->Dispatch(task.forget());
}
// Method to set up the decompression session.
nsresult InitializeSession();
nsresult OutputFrame(CFRefPtr<CVPixelBufferRef> aImage,
AppleFrameRef aFrameRef);
protected:
// Flush and Drain operation, always run
virtual void ProcessFlush();
virtual void ProcessDrain();
virtual void ProcessShutdown();
protected:
AppleFrameRef* CreateAppleFrameRef(const MediaRawData* aSample);
void DrainReorderedFrames();
void ClearReorderedFrames();
CFDictionaryRef CreateOutputConfiguration();
nsresult InitDecoder();
nsRefPtr<MediaByteBuffer> mExtraData;
nsRefPtr<FlushableTaskQueue> mTaskQueue;
@ -103,12 +114,27 @@ public:
uint32_t mDisplayWidth;
uint32_t mDisplayHeight;
uint32_t mMaxRefFrames;
// Increased when Input is called, and decreased when ProcessFrame runs.
// Reaching 0 indicates that there's no pending Input.
Atomic<uint32_t> mInputIncoming;
Atomic<bool> mIsShutDown;
bool mUseSoftwareImages;
bool mIs106;
// For wait on mIsFlushing during Shutdown() process.
Monitor mMonitor;
// Set on reader/decode thread calling Flush() to indicate that output is
// not required and so input samples on mTaskQueue need not be processed.
// Cleared on mTaskQueue in ProcessDrain().
Atomic<bool> mIsFlushing;
private:
VDADecoder mDecoder;
// Method to set up the decompression session.
nsresult InitializeSession();
// Method to pass a frame to VideoToolbox for decoding.
nsresult SubmitFrame(MediaRawData* aSample);
CFDictionaryRef CreateDecoderSpecification();

View File

@ -63,8 +63,8 @@ AppleVTDecoder::Init()
return InitPromise::CreateAndReject(DecoderFailureReason::INIT_ERROR, __func__);
}
nsresult
AppleVTDecoder::Shutdown()
void
AppleVTDecoder::ProcessShutdown()
{
if (mSession) {
LOG("%s: cleaning up session %p", __func__, mSession);
@ -77,12 +77,13 @@ AppleVTDecoder::Shutdown()
CFRelease(mFormat);
mFormat = nullptr;
}
return NS_OK;
}
nsresult
AppleVTDecoder::Input(MediaRawData* aSample)
{
MOZ_ASSERT(mCallback->OnReaderTaskQueue());
LOG("mp4 input sample %p pts %lld duration %lld us%s %d bytes",
aSample,
aSample->mTime,
@ -102,33 +103,34 @@ AppleVTDecoder::Input(MediaRawData* aSample)
LOG(" sha1 %s", digest.get());
#endif // LOG_MEDIA_SHA1
mInputIncoming++;
nsCOMPtr<nsIRunnable> runnable =
NS_NewRunnableMethodWithArg<nsRefPtr<MediaRawData>>(
this,
&AppleVTDecoder::SubmitFrame,
nsRefPtr<MediaRawData>(aSample));
this, &AppleVTDecoder::SubmitFrame, aSample);
mTaskQueue->Dispatch(runnable.forget());
return NS_OK;
}
nsresult
AppleVTDecoder::Flush()
void
AppleVTDecoder::ProcessFlush()
{
mTaskQueue->Flush();
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
nsresult rv = WaitForAsynchronousFrames();
if (NS_FAILED(rv)) {
LOG("AppleVTDecoder::Flush failed waiting for platform decoder "
"with error:%d.", rv);
}
ClearReorderedFrames();
return rv;
MonitorAutoLock mon(mMonitor);
mIsFlushing = false;
mon.NotifyAll();
}
nsresult
AppleVTDecoder::Drain()
void
AppleVTDecoder::ProcessDrain()
{
mTaskQueue->AwaitIdle();
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
nsresult rv = WaitForAsynchronousFrames();
if (NS_FAILED(rv)) {
LOG("AppleVTDecoder::Drain failed waiting for platform decoder "
@ -136,7 +138,6 @@ AppleVTDecoder::Drain()
}
DrainReorderedFrames();
mCallback->DrainComplete();
return NS_OK;
}
//
@ -174,9 +175,10 @@ PlatformCallback(void* decompressionOutputRefCon,
MOZ_ASSERT(CFGetTypeID(image) == CVPixelBufferGetTypeID(),
"VideoToolbox returned an unexpected image type");
// Forward the data back to an object method which can access
// the correct MP4Reader callback.
decoder->OutputFrame(image, frameRef);
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableMethodWithArgs<CFRefPtr<CVPixelBufferRef>, AppleVTDecoder::AppleFrameRef>(
decoder, &AppleVTDecoder::OutputFrame, image, *frameRef);
decoder->DispatchOutputTask(task.forget());
}
nsresult
@ -208,6 +210,8 @@ TimingInfoFromSample(MediaRawData* aSample)
nsresult
AppleVTDecoder::SubmitFrame(MediaRawData* aSample)
{
MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
mInputIncoming--;
// For some reason this gives me a double-free error with stagefright.
AutoCFRelease<CMBlockBufferRef> block = nullptr;
AutoCFRelease<CMSampleBufferRef> sample = nullptr;
@ -253,7 +257,7 @@ AppleVTDecoder::SubmitFrame(MediaRawData* aSample)
}
// Ask for more data.
if (mTaskQueue->IsEmpty()) {
if (!mInputIncoming) {
LOG("AppleVTDecoder task queue empty; requesting more data");
mCallback->InputExhausted();
}

View File

@ -22,14 +22,16 @@ public:
virtual ~AppleVTDecoder();
virtual nsRefPtr<InitPromise> Init() override;
virtual nsresult Input(MediaRawData* aSample) override;
virtual nsresult Flush() override;
virtual nsresult Drain() override;
virtual nsresult Shutdown() override;
virtual bool IsHardwareAccelerated() const override
{
return mIsHardwareAccelerated;
}
protected:
void ProcessFlush() override;
void ProcessDrain() override;
void ProcessShutdown() override;
private:
CMVideoFormatDescriptionRef mFormat;
VTDecompressionSessionRef mSession;