Merge remote-tracking branch 'origin/stable' into dev

Change-Id: I4fea8b03bc8baaa97e95413f05d13f0f505705d3
This commit is contained in:
Frederik Gladhorn
2013-12-16 15:36:55 +01:00
12 changed files with 167 additions and 399 deletions

View File

@@ -175,8 +175,6 @@ void QCameraPrivate::_q_error(int error, const QString &errorString)
void QCameraPrivate::setState(QCamera::State newState) void QCameraPrivate::setState(QCamera::State newState)
{ {
Q_Q(QCamera);
unsetError(); unsetError();
if (!control) { if (!control) {
@@ -184,13 +182,8 @@ void QCameraPrivate::setState(QCamera::State newState)
return; return;
} }
if (state == newState)
return;
restartPending = false; restartPending = false;
state = newState; control->setState(newState);
control->setState(state);
emit q->stateChanged(state);
} }
void QCameraPrivate::_q_updateState(QCamera::State newState) void QCameraPrivate::_q_updateState(QCamera::State newState)
@@ -203,7 +196,6 @@ void QCameraPrivate::_q_updateState(QCamera::State newState)
return; return;
if (newState != state) { if (newState != state) {
qDebug() << "Camera state changed:" << newState;
state = newState; state = newState;
emit q->stateChanged(state); emit q->stateChanged(state);
} }

View File

@@ -68,7 +68,8 @@ ANDROID_FEATURES += \
MODULE_PLUGIN_TYPES = \ MODULE_PLUGIN_TYPES = \
mediaservice \ mediaservice \
audio \ audio \
video/videonode video/videonode \
playlistformats
win32: LIBS_PRIVATE += -luuid win32: LIBS_PRIVATE += -luuid

View File

@@ -42,8 +42,11 @@
package org.qtproject.qt5.android.multimedia; package org.qtproject.qt5.android.multimedia;
import android.hardware.Camera; import android.hardware.Camera;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.util.Log; import android.util.Log;
import java.lang.Math;
import java.util.concurrent.locks.ReentrantLock;
public class QtCamera implements Camera.ShutterCallback, public class QtCamera implements Camera.ShutterCallback,
Camera.PictureCallback, Camera.PictureCallback,
@@ -52,6 +55,11 @@ public class QtCamera implements Camera.ShutterCallback,
{ {
private int m_cameraId = -1; private int m_cameraId = -1;
private Camera m_camera = null; private Camera m_camera = null;
private byte[] m_cameraPreviewFirstBuffer = null;
private byte[] m_cameraPreviewSecondBuffer = null;
private int m_actualPreviewBuffer = 0;
private final ReentrantLock m_buffersLock = new ReentrantLock();
private boolean m_isReleased = false;
private static final String TAG = "Qt Camera"; private static final String TAG = "Qt Camera";
@@ -97,6 +105,7 @@ public class QtCamera implements Camera.ShutterCallback,
public void release() public void release()
{ {
m_isReleased = true;
m_camera.release(); m_camera.release();
} }
@@ -134,6 +143,22 @@ public class QtCamera implements Camera.ShutterCallback,
public void startPreview() public void startPreview()
{ {
Camera.Size previewSize = m_camera.getParameters().getPreviewSize();
double bytesPerPixel = ImageFormat.getBitsPerPixel(m_camera.getParameters().getPreviewFormat()) / 8.0;
int bufferSizeNeeded = (int)Math.ceil(bytesPerPixel*previewSize.width*previewSize.height);
//We need to clear preview buffers queue here, but there is no method to do it
//Though just resetting preview callback do the trick
m_camera.setPreviewCallback(null);
m_buffersLock.lock();
if (m_cameraPreviewFirstBuffer == null || m_cameraPreviewFirstBuffer.length < bufferSizeNeeded)
m_cameraPreviewFirstBuffer = new byte[bufferSizeNeeded];
if (m_cameraPreviewSecondBuffer == null || m_cameraPreviewSecondBuffer.length < bufferSizeNeeded)
m_cameraPreviewSecondBuffer = new byte[bufferSizeNeeded];
addCallbackBuffer();
m_buffersLock.unlock();
m_camera.setPreviewCallbackWithBuffer(this);
m_camera.startPreview(); m_camera.startPreview();
} }
@@ -152,11 +177,6 @@ public class QtCamera implements Camera.ShutterCallback,
m_camera.cancelAutoFocus(); m_camera.cancelAutoFocus();
} }
public void requestPreviewFrame()
{
m_camera.setOneShotPreviewCallback(this);
}
public void takePicture() public void takePicture()
{ {
try { try {
@@ -166,6 +186,37 @@ public class QtCamera implements Camera.ShutterCallback,
} }
} }
public byte[] lockAndFetchPreviewBuffer()
{
//This method should always be followed by unlockPreviewBuffer()
//This method is not just a getter. It also marks last preview as already seen one.
//We should reset actualBuffer flag here to make sure we will not use old preview with future captures
byte[] result = null;
m_buffersLock.lock();
if (m_actualPreviewBuffer == 1)
result = m_cameraPreviewFirstBuffer;
else if (m_actualPreviewBuffer == 2)
result = m_cameraPreviewSecondBuffer;
m_actualPreviewBuffer = 0;
return result;
}
public void unlockPreviewBuffer()
{
if (m_buffersLock.isHeldByCurrentThread())
m_buffersLock.unlock();
}
private void addCallbackBuffer()
{
if (m_isReleased)
return;
m_camera.addCallbackBuffer((m_actualPreviewBuffer == 1)
? m_cameraPreviewSecondBuffer
: m_cameraPreviewFirstBuffer);
}
@Override @Override
public void onShutter() public void onShutter()
{ {
@@ -181,7 +232,15 @@ public class QtCamera implements Camera.ShutterCallback,
@Override @Override
public void onPreviewFrame(byte[] data, Camera camera) public void onPreviewFrame(byte[] data, Camera camera)
{ {
notifyPreviewFrame(m_cameraId, data); m_buffersLock.lock();
if (data == m_cameraPreviewFirstBuffer)
m_actualPreviewBuffer = 1;
else if (data == m_cameraPreviewSecondBuffer)
m_actualPreviewBuffer = 2;
else
m_actualPreviewBuffer = 0;
addCallbackBuffer();
m_buffersLock.unlock();
} }
@Override @Override
@@ -193,5 +252,4 @@ public class QtCamera implements Camera.ShutterCallback,
private static native void notifyAutoFocusComplete(int id, boolean success); private static native void notifyAutoFocusComplete(int id, boolean success);
private static native void notifyPictureExposed(int id); private static native void notifyPictureExposed(int id);
private static native void notifyPictureCaptured(int id, byte[] data); private static native void notifyPictureCaptured(int id, byte[] data);
private static native void notifyPreviewFrame(int id, byte[] data);
} }

View File

@@ -92,6 +92,7 @@ QAndroidCameraSession::QAndroidCameraSession(QObject *parent)
, m_selectedCamera(0) , m_selectedCamera(0)
, m_camera(0) , m_camera(0)
, m_nativeOrientation(0) , m_nativeOrientation(0)
, m_previewOrientation(0)
, m_videoOutput(0) , m_videoOutput(0)
, m_captureMode(QCamera::CaptureViewfinder) , m_captureMode(QCamera::CaptureViewfinder)
, m_state(QCamera::UnloadedState) , m_state(QCamera::UnloadedState)
@@ -182,11 +183,20 @@ bool QAndroidCameraSession::open()
if (m_camera) { if (m_camera) {
connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed())); connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed()));
connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray))); connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray)));
connect(m_camera, SIGNAL(previewFrameAvailable(QByteArray)), this, SLOT(onCameraPreviewFrameAvailable(QByteArray)));
m_nativeOrientation = m_camera->getNativeOrientation(); m_nativeOrientation = m_camera->getNativeOrientation();
// Preview orientation will always match the device natural orientation
if (m_camera->getFacing() == JCamera::CameraFacingFront)
m_previewOrientation = 360 - m_nativeOrientation;
else
m_previewOrientation = m_nativeOrientation;
m_status = QCamera::LoadedStatus; m_status = QCamera::LoadedStatus;
if (m_camera->getPreviewFormat() != JCamera::NV21) if (m_camera->getPreviewFormat() != JCamera::NV21)
m_camera->setPreviewFormat(JCamera::NV21); m_camera->setPreviewFormat(JCamera::NV21);
emit opened(); emit opened();
} else { } else {
m_status = QCamera::UnavailableStatus; m_status = QCamera::UnavailableStatus;
@@ -257,8 +267,16 @@ void QAndroidCameraSession::adjustViewfinderSize(const QSize &captureSize, bool
} }
if (m_camera->previewSize() != viewfinderResolution) { if (m_camera->previewSize() != viewfinderResolution) {
if (m_videoOutput) if (m_videoOutput) {
m_videoOutput->setVideoSize(viewfinderResolution); QSize size = viewfinderResolution;
// If the preview orientation is not the defaut one (0 or 180 degrees),
// we have to invert the output aspect ratio.
if (m_previewOrientation % 180)
size.transpose();
m_videoOutput->setVideoSize(size);
}
// if preview is started, we have to stop it first before changing its size // if preview is started, we have to stop it first before changing its size
if (m_previewStarted && restartPreview) if (m_previewStarted && restartPreview)
@@ -282,6 +300,7 @@ void QAndroidCameraSession::startPreview()
applyImageSettings(); applyImageSettings();
adjustViewfinderSize(m_imageSettings.resolution()); adjustViewfinderSize(m_imageSettings.resolution());
m_camera->setDisplayOrientation(m_previewOrientation);
if (m_videoOutput && m_videoOutput->isReady()) if (m_videoOutput && m_videoOutput->isReady())
onVideoOutputReady(true); onVideoOutputReady(true);
@@ -464,7 +483,6 @@ int QAndroidCameraSession::capture(const QString &fileName)
// adjust picture rotation depending on the device orientation // adjust picture rotation depending on the device orientation
m_camera->setRotation(currentCameraRotation()); m_camera->setRotation(currentCameraRotation());
m_camera->requestPreviewFrame();
m_camera->takePicture(); m_camera->takePicture();
} else { } else {
//: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc. //: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc.
@@ -489,6 +507,13 @@ void QAndroidCameraSession::onCameraPictureExposed()
return; return;
emit imageExposed(m_currentImageCaptureId); emit imageExposed(m_currentImageCaptureId);
QByteArray lastFrame = m_camera->fetchLastPreviewFrame();
if (lastFrame.size()) {
QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage,
m_currentImageCaptureId,
lastFrame,
m_camera->getRotation());
}
} }
void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data) void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data)
@@ -551,17 +576,7 @@ void QAndroidCameraSession::processCapturedImage(int id,
} }
} }
void QAndroidCameraSession::onCameraPreviewFrameAvailable(const QByteArray &data) void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation)
{
if (m_captureCanceled || m_readyForCapture)
return;
QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage,
m_currentImageCaptureId,
data);
}
void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data)
{ {
QSize frameSize = m_camera->previewSize(); QSize frameSize = m_camera->previewSize();
QImage preview(frameSize, QImage::Format_ARGB32); QImage preview(frameSize, QImage::Format_ARGB32);
@@ -570,11 +585,17 @@ void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data)
frameSize.width(), frameSize.width(),
frameSize.height()); frameSize.height());
QTransform transform;
// Preview display of front-facing cameras is flipped horizontally, but the frame data // Preview display of front-facing cameras is flipped horizontally, but the frame data
// we get here is not. Flip it ourselves if the camera is front-facing to match what the user // we get here is not. Flip it ourselves if the camera is front-facing to match what the user
// sees on the viewfinder. // sees on the viewfinder.
if (m_camera->getFacing() == JCamera::CameraFacingFront) if (m_camera->getFacing() == JCamera::CameraFacingFront)
preview = preview.transformed(QTransform().scale(-1, 1)); transform.scale(-1, 1);
transform.rotate(rotation);
preview = preview.transformed(transform);
emit imageCaptured(id, preview); emit imageCaptured(id, preview);
} }

View File

@@ -114,7 +114,6 @@ private Q_SLOTS:
void onCameraPictureExposed(); void onCameraPictureExposed();
void onCameraPictureCaptured(const QByteArray &data); void onCameraPictureCaptured(const QByteArray &data);
void onCameraPreviewFrameAvailable(const QByteArray &data);
private: private:
bool open(); bool open();
@@ -124,7 +123,7 @@ private:
void stopPreview(); void stopPreview();
void applyImageSettings(); void applyImageSettings();
void processPreviewImage(int id, const QByteArray &data); void processPreviewImage(int id, const QByteArray &data, int rotation);
void processCapturedImage(int id, void processCapturedImage(int id,
const QByteArray &data, const QByteArray &data,
const QSize &resolution, const QSize &resolution,
@@ -134,6 +133,7 @@ private:
int m_selectedCamera; int m_selectedCamera;
JCamera *m_camera; JCamera *m_camera;
int m_nativeOrientation; int m_nativeOrientation;
int m_previewOrientation;
QAndroidVideoOutput *m_videoOutput; QAndroidVideoOutput *m_videoOutput;
QCamera::CaptureModes m_captureMode; QCamera::CaptureModes m_captureMode;

View File

@@ -218,10 +218,19 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
return; return;
} }
const QString uri = mediaContent.canonicalUrl().toString(); const QUrl url = mediaContent.canonicalUrl();
QString mediaPath;
if (url.scheme() == QLatin1String("qrc")) {
const QString path = url.toString().mid(3);
mTempFile.reset(QTemporaryFile::createNativeFile(path));
if (!mTempFile.isNull())
mediaPath = QLatin1String("file://") + mTempFile->fileName();
} else {
mediaPath = url.toString();
}
if (!uri.isEmpty()) if (!mediaPath.isEmpty())
mMediaPlayer->setDataSource(uri); mMediaPlayer->setDataSource(mediaPath);
else else
setMediaStatus(QMediaPlayer::NoMedia); setMediaStatus(QMediaPlayer::NoMedia);

View File

@@ -45,6 +45,7 @@
#include <qglobal.h> #include <qglobal.h>
#include <QMediaPlayerControl> #include <QMediaPlayerControl>
#include <qsize.h> #include <qsize.h>
#include <QtCore/QTemporaryFile>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -114,6 +115,7 @@ private:
QMediaPlayer::State mPendingState; QMediaPlayer::State mPendingState;
qint64 mPendingPosition; qint64 mPendingPosition;
bool mPendingSetMedia; bool mPendingSetMedia;
QScopedPointer<QTemporaryFile> mTempFile;
void setState(QMediaPlayer::State state); void setState(QMediaPlayer::State state);
void setMediaStatus(QMediaPlayer::MediaStatus status); void setMediaStatus(QMediaPlayer::MediaStatus status);

View File

@@ -102,22 +102,11 @@ static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data)
} }
} }
static void notifyPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data)
{
JCamera *obj = g_objectMap.value(id, 0);
if (obj) {
QByteArray bytes;
int arrayLength = env->GetArrayLength(data);
bytes.resize(arrayLength);
env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data());
Q_EMIT obj->previewFrameAvailable(bytes);
}
}
JCamera::JCamera(int cameraId, jobject cam) JCamera::JCamera(int cameraId, jobject cam)
: QObject() : QObject()
, QJNIObjectPrivate(cam) , QJNIObjectPrivate(cam)
, m_cameraId(cameraId) , m_cameraId(cameraId)
, m_rotation(0)
, m_hasAPI14(false) , m_hasAPI14(false)
{ {
if (isValid()) { if (isValid()) {
@@ -205,6 +194,11 @@ int JCamera::getNativeOrientation()
return m_info.getField<jint>("orientation"); return m_info.getField<jint>("orientation");
} }
void JCamera::setDisplayOrientation(int degrees)
{
callMethod<void>("setDisplayOrientation", "(I)V", degrees);
}
QSize JCamera::getPreferredPreviewSizeForVideo() QSize JCamera::getPreferredPreviewSizeForVideo()
{ {
if (!m_parameters.isValid()) if (!m_parameters.isValid())
@@ -612,10 +606,16 @@ void JCamera::setRotation(int rotation)
if (!m_parameters.isValid()) if (!m_parameters.isValid())
return; return;
m_rotation = rotation;
m_parameters.callMethod<void>("setRotation", "(I)V", rotation); m_parameters.callMethod<void>("setRotation", "(I)V", rotation);
applyParameters(); applyParameters();
} }
int JCamera::getRotation() const
{
return m_rotation;
}
QList<QSize> JCamera::getSupportedPictureSizes() QList<QSize> JCamera::getSupportedPictureSizes()
{ {
QList<QSize> list; QList<QSize> list;
@@ -655,16 +655,28 @@ void JCamera::setJpegQuality(int quality)
applyParameters(); applyParameters();
} }
void JCamera::requestPreviewFrame()
{
callMethod<void>("requestPreviewFrame");
}
void JCamera::takePicture() void JCamera::takePicture()
{ {
callMethod<void>("takePicture"); callMethod<void>("takePicture");
} }
QByteArray JCamera::fetchLastPreviewFrame()
{
QJNIEnvironmentPrivate env;
QJNIObjectPrivate dataObj = callObjectMethod("lockAndFetchPreviewBuffer", "()[B");
if (!dataObj.object()) {
callMethod<void>("unlockPreviewBuffer");
return QByteArray();
}
jbyteArray data = static_cast<jbyteArray>(dataObj.object());
QByteArray bytes;
int arrayLength = env->GetArrayLength(data);
bytes.resize(arrayLength);
env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data());
callMethod<void>("unlockPreviewBuffer");
return bytes;
}
void JCamera::startPreview() void JCamera::startPreview()
{ {
callMethod<void>("startPreview"); callMethod<void>("startPreview");
@@ -708,8 +720,7 @@ QStringList JCamera::callStringListMethod(const char *methodName)
static JNINativeMethod methods[] = { static JNINativeMethod methods[] = {
{"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete}, {"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete},
{"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed}, {"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed},
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}, {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}
{"notifyPreviewFrame", "(I[B)V", (void *)notifyPreviewFrame}
}; };
bool JCamera::initJNI(JNIEnv *env) bool JCamera::initJNI(JNIEnv *env)

View File

@@ -82,6 +82,8 @@ public:
CameraFacing getFacing(); CameraFacing getFacing();
int getNativeOrientation(); int getNativeOrientation();
void setDisplayOrientation(int degrees);
QSize getPreferredPreviewSizeForVideo(); QSize getPreferredPreviewSizeForVideo();
QList<QSize> getSupportedPreviewSizes(); QList<QSize> getSupportedPreviewSizes();
@@ -136,6 +138,7 @@ public:
void setWhiteBalance(const QString &value); void setWhiteBalance(const QString &value);
void setRotation(int rotation); void setRotation(int rotation);
int getRotation() const;
QList<QSize> getSupportedPictureSizes(); QList<QSize> getSupportedPictureSizes();
void setPictureSize(const QSize &size); void setPictureSize(const QSize &size);
@@ -144,10 +147,10 @@ public:
void startPreview(); void startPreview();
void stopPreview(); void stopPreview();
void requestPreviewFrame();
void takePicture(); void takePicture();
QByteArray fetchLastPreviewFrame();
static bool initJNI(JNIEnv *env); static bool initJNI(JNIEnv *env);
Q_SIGNALS: Q_SIGNALS:
@@ -158,8 +161,6 @@ Q_SIGNALS:
void whiteBalanceChanged(); void whiteBalanceChanged();
void previewFrameAvailable(const QByteArray &data);
void pictureExposed(); void pictureExposed();
void pictureCaptured(const QByteArray &data); void pictureCaptured(const QByteArray &data);
@@ -174,6 +175,7 @@ private:
QJNIObjectPrivate m_parameters; QJNIObjectPrivate m_parameters;
QSize m_previewSize; QSize m_previewSize;
int m_rotation;
bool m_hasAPI14; bool m_hasAPI14;
}; };

View File

@@ -68,339 +68,6 @@
#include <wmcodecdsp.h> #include <wmcodecdsp.h>
//#define DEBUG_MEDIAFOUNDATION //#define DEBUG_MEDIAFOUNDATION
//#define TEST_STREAMING
namespace
{
//MFStream is added for supporting QIODevice type of media source.
//It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice.
class MFStream : public QObject, public IMFByteStream
{
Q_OBJECT
public:
MFStream(QIODevice *stream, bool ownStream)
: m_cRef(1)
, m_stream(stream)
, m_ownStream(ownStream)
, m_currentReadResult(0)
{
//Move to the thread of the stream object
//to make sure invocations on stream
//are happened in the same thread of stream object
this->moveToThread(stream->thread());
connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead()));
}
~MFStream()
{
if (m_currentReadResult)
m_currentReadResult->Release();
if (m_ownStream)
m_stream->deleteLater();
}
//from IUnknown
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
if (riid == IID_IMFByteStream) {
*ppvObject = static_cast<IMFByteStream*>(this);
} else if (riid == IID_IUnknown) {
*ppvObject = static_cast<IUnknown*>(this);
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Release(void)
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0) {
this->deleteLater();
}
return cRef;
}
//from IMFByteStream
STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities)
{
if (!pdwCapabilities)
return E_INVALIDARG;
*pdwCapabilities = MFBYTESTREAM_IS_READABLE;
if (!m_stream->isSequential())
*pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE;
return S_OK;
}
STDMETHODIMP GetLength(QWORD *pqwLength)
{
if (!pqwLength)
return E_INVALIDARG;
QMutexLocker locker(&m_mutex);
*pqwLength = QWORD(m_stream->size());
return S_OK;
}
STDMETHODIMP SetLength(QWORD)
{
return E_NOTIMPL;
}
STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition)
{
if (!pqwPosition)
return E_INVALIDARG;
QMutexLocker locker(&m_mutex);
*pqwPosition = m_stream->pos();
return S_OK;
}
STDMETHODIMP SetCurrentPosition(QWORD qwPosition)
{
QMutexLocker locker(&m_mutex);
//SetCurrentPosition may happend during the BeginRead/EndRead pair,
//refusing to execute SetCurrentPosition during that time seems to be
//the simplest workable solution
if (m_currentReadResult)
return S_FALSE;
bool seekOK = m_stream->seek(qint64(qwPosition));
if (seekOK)
return S_OK;
else
return S_FALSE;
}
STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream)
{
if (!pfEndOfStream)
return E_INVALIDARG;
QMutexLocker locker(&m_mutex);
*pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE;
return S_OK;
}
STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead)
{
QMutexLocker locker(&m_mutex);
qint64 read = m_stream->read((char*)(pb), qint64(cb));
if (pcbRead)
*pcbRead = ULONG(read);
return S_OK;
}
STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback,
IUnknown *punkState)
{
if (!pCallback || !pb)
return E_INVALIDARG;
Q_ASSERT(m_currentReadResult == NULL);
AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb);
if (state == NULL)
return E_OUTOFMEMORY;
HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult);
state->Release();
if (FAILED(hr))
return hr;
QCoreApplication::postEvent(this, new QEvent(QEvent::User));
return hr;
}
STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead)
{
if (!pcbRead)
return E_INVALIDARG;
IUnknown *pUnk;
pResult->GetObject(&pUnk);
AsyncReadState *state = static_cast<AsyncReadState*>(pUnk);
*pcbRead = state->bytesRead();
pUnk->Release();
m_currentReadResult->Release();
m_currentReadResult = NULL;
return S_OK;
}
STDMETHODIMP Write(const BYTE *, ULONG, ULONG *)
{
return E_NOTIMPL;
}
STDMETHODIMP BeginWrite(const BYTE *, ULONG ,
IMFAsyncCallback *,
IUnknown *)
{
return E_NOTIMPL;
}
STDMETHODIMP EndWrite(IMFAsyncResult *,
ULONG *)
{
return E_NOTIMPL;
}
STDMETHODIMP Seek(
MFBYTESTREAM_SEEK_ORIGIN SeekOrigin,
LONGLONG llSeekOffset,
DWORD,
QWORD *pqwCurrentPosition)
{
QMutexLocker locker(&m_mutex);
if (m_currentReadResult)
return S_FALSE;
qint64 pos = qint64(llSeekOffset);
switch (SeekOrigin) {
case msoCurrent:
pos += m_stream->pos();
break;
}
bool seekOK = m_stream->seek(pos);
if (*pqwCurrentPosition)
*pqwCurrentPosition = pos;
if (seekOK)
return S_OK;
else
return S_FALSE;
}
STDMETHODIMP Flush()
{
return E_NOTIMPL;
}
STDMETHODIMP Close()
{
QMutexLocker locker(&m_mutex);
if (m_ownStream)
m_stream->close();
return S_OK;
}
private:
//AsyncReadState is a helper class used in BeginRead for asynchronous operation
//to record some BeginRead parameters, so these parameters could be
//used later when actually executing the read operation in another thread.
class AsyncReadState : public IUnknown
{
public:
AsyncReadState(BYTE *pb, ULONG cb)
: m_cRef(1)
, m_pb(pb)
, m_cb(cb)
, m_cbRead(0)
{
}
//from IUnknown
STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject)
{
if (!ppvObject)
return E_POINTER;
if (riid == IID_IUnknown) {
*ppvObject = static_cast<IUnknown*>(this);
} else {
*ppvObject = NULL;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) Release(void)
{
LONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
delete this;
// For thread safety, return a temporary variable.
return cRef;
}
BYTE* pb() const { return m_pb; }
ULONG cb() const { return m_cb; }
ULONG bytesRead() const { return m_cbRead; }
void setBytesRead(ULONG cbRead) { m_cbRead = cbRead; }
private:
long m_cRef;
BYTE *m_pb;
ULONG m_cb;
ULONG m_cbRead;
};
long m_cRef;
QIODevice *m_stream;
bool m_ownStream;
DWORD m_workQueueId;
QMutex m_mutex;
void doRead()
{
bool readDone = true;
IUnknown *pUnk = NULL;
HRESULT hr = m_currentReadResult->GetObject(&pUnk);
if (SUCCEEDED(hr)) {
//do actual read
AsyncReadState *state = static_cast<AsyncReadState*>(pUnk);
ULONG cbRead;
Read(state->pb(), state->cb() - state->bytesRead(), &cbRead);
pUnk->Release();
state->setBytesRead(cbRead + state->bytesRead());
if (state->cb() > state->bytesRead() && !m_stream->atEnd()) {
readDone = false;
}
}
if (readDone) {
//now inform the original caller
m_currentReadResult->SetStatus(hr);
MFInvokeCallback(m_currentReadResult);
}
}
private Q_SLOTS:
void handleReadyRead()
{
doRead();
}
protected:
void customEvent(QEvent *event)
{
if (event->type() != QEvent::User) {
QObject::customEvent(event);
return;
}
doRead();
}
IMFAsyncResult *m_currentReadResult;
};
}
MFPlayerSession::MFPlayerSession(MFPlayerService *playerService) MFPlayerSession::MFPlayerSession(MFPlayerService *playerService)
: m_playerService(playerService) : m_playerService(playerService)
@@ -411,6 +78,8 @@ MFPlayerSession::MFPlayerSession(MFPlayerService *playerService)
, m_rateSupport(0) , m_rateSupport(0)
, m_volumeControl(0) , m_volumeControl(0)
, m_netsourceStatistics(0) , m_netsourceStatistics(0)
, m_duration(0)
, m_sourceResolver(0)
, m_hCloseEvent(0) , m_hCloseEvent(0)
, m_closing(false) , m_closing(false)
, m_pendingRate(1) , m_pendingRate(1)
@@ -536,7 +205,7 @@ void MFPlayerSession::load(const QMediaContent &media, QIODevice *stream)
clear(); clear();
QMediaResourceList resources = media.resources(); QMediaResourceList resources = media.resources();
if (m_status == QMediaPlayer::LoadingMedia) if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver)
m_sourceResolver->cancel(); m_sourceResolver->cancel();
if (resources.isEmpty() && !stream) { if (resources.isEmpty() && !stream) {
@@ -581,7 +250,7 @@ void MFPlayerSession::handleSourceError(long hr)
void MFPlayerSession::handleMediaSourceReady() void MFPlayerSession::handleMediaSourceReady()
{ {
if (QMediaPlayer::LoadingMedia != m_status) if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver)
return; return;
#ifdef DEBUG_MEDIAFOUNDATION #ifdef DEBUG_MEDIAFOUNDATION
qDebug() << "handleMediaSourceReady"; qDebug() << "handleMediaSourceReady";
@@ -1786,7 +1455,7 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent)
{ {
HRESULT hrStatus = S_OK; HRESULT hrStatus = S_OK;
HRESULT hr = sessionEvent->GetStatus(&hrStatus); HRESULT hr = sessionEvent->GetStatus(&hrStatus);
if (FAILED(hr)) { if (FAILED(hr) || !m_session) {
sessionEvent->Release(); sessionEvent->Release();
return; return;
} }

View File

@@ -1085,6 +1085,9 @@ namespace
HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult) HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult)
{ {
QMutexLocker locker(&m_mutex); QMutexLocker locker(&m_mutex);
if (m_shutdown)
return MF_E_SHUTDOWN;
HRESULT hr = S_OK; HRESULT hr = S_OK;
IUnknown *pState = NULL; IUnknown *pState = NULL;
hr = pAsyncResult->GetState(&pState); hr = pAsyncResult->GetState(&pState);

View File

@@ -197,7 +197,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream)
#ifdef TEST_STREAMING #ifdef TEST_STREAMING
//Testing stream function //Testing stream function
if (url.scheme() == QLatin1String("file")) { if (url.scheme() == QLatin1String("file")) {
stream = new QFile(url.path().mid(1), this); stream = new QFile(url.path().mid(1));
if (stream->open(QIODevice::ReadOnly)) { if (stream->open(QIODevice::ReadOnly)) {
m_stream = new MFStream(stream, true); m_stream = new MFStream(stream, true);
hr = m_sourceResolver->BeginCreateObjectFromByteStream( hr = m_sourceResolver->BeginCreateObjectFromByteStream(
@@ -217,7 +217,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream)
if (url.scheme() == QLatin1String("qrc")) { if (url.scheme() == QLatin1String("qrc")) {
// If the canonical URL refers to a Qt resource, open with QFile and use // If the canonical URL refers to a Qt resource, open with QFile and use
// the stream playback capability to play. // the stream playback capability to play.
stream = new QFile(QLatin1Char(':') + url.path(), this); stream = new QFile(QLatin1Char(':') + url.path());
if (stream->open(QIODevice::ReadOnly)) { if (stream->open(QIODevice::ReadOnly)) {
m_stream = new MFStream(stream, true); m_stream = new MFStream(stream, true);
hr = m_sourceResolver->BeginCreateObjectFromByteStream( hr = m_sourceResolver->BeginCreateObjectFromByteStream(