Merge remote-tracking branch 'origin/stable' into dev
Change-Id: I4fea8b03bc8baaa97e95413f05d13f0f505705d3
This commit is contained in:
@@ -175,8 +175,6 @@ void QCameraPrivate::_q_error(int error, const QString &errorString)
|
||||
|
||||
void QCameraPrivate::setState(QCamera::State newState)
|
||||
{
|
||||
Q_Q(QCamera);
|
||||
|
||||
unsetError();
|
||||
|
||||
if (!control) {
|
||||
@@ -184,13 +182,8 @@ void QCameraPrivate::setState(QCamera::State newState)
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == newState)
|
||||
return;
|
||||
|
||||
restartPending = false;
|
||||
state = newState;
|
||||
control->setState(state);
|
||||
emit q->stateChanged(state);
|
||||
control->setState(newState);
|
||||
}
|
||||
|
||||
void QCameraPrivate::_q_updateState(QCamera::State newState)
|
||||
@@ -203,7 +196,6 @@ void QCameraPrivate::_q_updateState(QCamera::State newState)
|
||||
return;
|
||||
|
||||
if (newState != state) {
|
||||
qDebug() << "Camera state changed:" << newState;
|
||||
state = newState;
|
||||
emit q->stateChanged(state);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ ANDROID_FEATURES += \
|
||||
MODULE_PLUGIN_TYPES = \
|
||||
mediaservice \
|
||||
audio \
|
||||
video/videonode
|
||||
video/videonode \
|
||||
playlistformats
|
||||
|
||||
win32: LIBS_PRIVATE += -luuid
|
||||
|
||||
|
||||
@@ -42,8 +42,11 @@
|
||||
package org.qtproject.qt5.android.multimedia;
|
||||
|
||||
import android.hardware.Camera;
|
||||
import android.graphics.ImageFormat;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.util.Log;
|
||||
import java.lang.Math;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class QtCamera implements Camera.ShutterCallback,
|
||||
Camera.PictureCallback,
|
||||
@@ -52,6 +55,11 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
{
|
||||
private int m_cameraId = -1;
|
||||
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";
|
||||
|
||||
@@ -97,6 +105,7 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
|
||||
public void release()
|
||||
{
|
||||
m_isReleased = true;
|
||||
m_camera.release();
|
||||
}
|
||||
|
||||
@@ -134,6 +143,22 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -152,11 +177,6 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
m_camera.cancelAutoFocus();
|
||||
}
|
||||
|
||||
public void requestPreviewFrame()
|
||||
{
|
||||
m_camera.setOneShotPreviewCallback(this);
|
||||
}
|
||||
|
||||
public void takePicture()
|
||||
{
|
||||
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
|
||||
public void onShutter()
|
||||
{
|
||||
@@ -181,7 +232,15 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
@Override
|
||||
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
|
||||
@@ -193,5 +252,4 @@ public class QtCamera implements Camera.ShutterCallback,
|
||||
private static native void notifyAutoFocusComplete(int id, boolean success);
|
||||
private static native void notifyPictureExposed(int id);
|
||||
private static native void notifyPictureCaptured(int id, byte[] data);
|
||||
private static native void notifyPreviewFrame(int id, byte[] data);
|
||||
}
|
||||
|
||||
@@ -92,6 +92,7 @@ QAndroidCameraSession::QAndroidCameraSession(QObject *parent)
|
||||
, m_selectedCamera(0)
|
||||
, m_camera(0)
|
||||
, m_nativeOrientation(0)
|
||||
, m_previewOrientation(0)
|
||||
, m_videoOutput(0)
|
||||
, m_captureMode(QCamera::CaptureViewfinder)
|
||||
, m_state(QCamera::UnloadedState)
|
||||
@@ -182,11 +183,20 @@ bool QAndroidCameraSession::open()
|
||||
if (m_camera) {
|
||||
connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed()));
|
||||
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();
|
||||
|
||||
// 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;
|
||||
|
||||
if (m_camera->getPreviewFormat() != JCamera::NV21)
|
||||
m_camera->setPreviewFormat(JCamera::NV21);
|
||||
|
||||
emit opened();
|
||||
} else {
|
||||
m_status = QCamera::UnavailableStatus;
|
||||
@@ -257,8 +267,16 @@ void QAndroidCameraSession::adjustViewfinderSize(const QSize &captureSize, bool
|
||||
}
|
||||
|
||||
if (m_camera->previewSize() != viewfinderResolution) {
|
||||
if (m_videoOutput)
|
||||
m_videoOutput->setVideoSize(viewfinderResolution);
|
||||
if (m_videoOutput) {
|
||||
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 (m_previewStarted && restartPreview)
|
||||
@@ -282,6 +300,7 @@ void QAndroidCameraSession::startPreview()
|
||||
|
||||
applyImageSettings();
|
||||
adjustViewfinderSize(m_imageSettings.resolution());
|
||||
m_camera->setDisplayOrientation(m_previewOrientation);
|
||||
|
||||
if (m_videoOutput && m_videoOutput->isReady())
|
||||
onVideoOutputReady(true);
|
||||
@@ -464,7 +483,6 @@ int QAndroidCameraSession::capture(const QString &fileName)
|
||||
// adjust picture rotation depending on the device orientation
|
||||
m_camera->setRotation(currentCameraRotation());
|
||||
|
||||
m_camera->requestPreviewFrame();
|
||||
m_camera->takePicture();
|
||||
} else {
|
||||
//: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc.
|
||||
@@ -489,6 +507,13 @@ void QAndroidCameraSession::onCameraPictureExposed()
|
||||
return;
|
||||
|
||||
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)
|
||||
@@ -551,17 +576,7 @@ void QAndroidCameraSession::processCapturedImage(int id,
|
||||
}
|
||||
}
|
||||
|
||||
void QAndroidCameraSession::onCameraPreviewFrameAvailable(const QByteArray &data)
|
||||
{
|
||||
if (m_captureCanceled || m_readyForCapture)
|
||||
return;
|
||||
|
||||
QtConcurrent::run(this, &QAndroidCameraSession::processPreviewImage,
|
||||
m_currentImageCaptureId,
|
||||
data);
|
||||
}
|
||||
|
||||
void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data)
|
||||
void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data, int rotation)
|
||||
{
|
||||
QSize frameSize = m_camera->previewSize();
|
||||
QImage preview(frameSize, QImage::Format_ARGB32);
|
||||
@@ -570,11 +585,17 @@ void QAndroidCameraSession::processPreviewImage(int id, const QByteArray &data)
|
||||
frameSize.width(),
|
||||
frameSize.height());
|
||||
|
||||
QTransform transform;
|
||||
|
||||
// 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
|
||||
// sees on the viewfinder.
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ private Q_SLOTS:
|
||||
|
||||
void onCameraPictureExposed();
|
||||
void onCameraPictureCaptured(const QByteArray &data);
|
||||
void onCameraPreviewFrameAvailable(const QByteArray &data);
|
||||
|
||||
private:
|
||||
bool open();
|
||||
@@ -124,7 +123,7 @@ private:
|
||||
void stopPreview();
|
||||
|
||||
void applyImageSettings();
|
||||
void processPreviewImage(int id, const QByteArray &data);
|
||||
void processPreviewImage(int id, const QByteArray &data, int rotation);
|
||||
void processCapturedImage(int id,
|
||||
const QByteArray &data,
|
||||
const QSize &resolution,
|
||||
@@ -134,6 +133,7 @@ private:
|
||||
int m_selectedCamera;
|
||||
JCamera *m_camera;
|
||||
int m_nativeOrientation;
|
||||
int m_previewOrientation;
|
||||
QAndroidVideoOutput *m_videoOutput;
|
||||
|
||||
QCamera::CaptureModes m_captureMode;
|
||||
|
||||
@@ -218,10 +218,19 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent,
|
||||
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())
|
||||
mMediaPlayer->setDataSource(uri);
|
||||
if (!mediaPath.isEmpty())
|
||||
mMediaPlayer->setDataSource(mediaPath);
|
||||
else
|
||||
setMediaStatus(QMediaPlayer::NoMedia);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <qglobal.h>
|
||||
#include <QMediaPlayerControl>
|
||||
#include <qsize.h>
|
||||
#include <QtCore/QTemporaryFile>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@@ -114,6 +115,7 @@ private:
|
||||
QMediaPlayer::State mPendingState;
|
||||
qint64 mPendingPosition;
|
||||
bool mPendingSetMedia;
|
||||
QScopedPointer<QTemporaryFile> mTempFile;
|
||||
|
||||
void setState(QMediaPlayer::State state);
|
||||
void setMediaStatus(QMediaPlayer::MediaStatus status);
|
||||
|
||||
@@ -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)
|
||||
: QObject()
|
||||
, QJNIObjectPrivate(cam)
|
||||
, m_cameraId(cameraId)
|
||||
, m_rotation(0)
|
||||
, m_hasAPI14(false)
|
||||
{
|
||||
if (isValid()) {
|
||||
@@ -205,6 +194,11 @@ int JCamera::getNativeOrientation()
|
||||
return m_info.getField<jint>("orientation");
|
||||
}
|
||||
|
||||
void JCamera::setDisplayOrientation(int degrees)
|
||||
{
|
||||
callMethod<void>("setDisplayOrientation", "(I)V", degrees);
|
||||
}
|
||||
|
||||
QSize JCamera::getPreferredPreviewSizeForVideo()
|
||||
{
|
||||
if (!m_parameters.isValid())
|
||||
@@ -612,10 +606,16 @@ void JCamera::setRotation(int rotation)
|
||||
if (!m_parameters.isValid())
|
||||
return;
|
||||
|
||||
m_rotation = rotation;
|
||||
m_parameters.callMethod<void>("setRotation", "(I)V", rotation);
|
||||
applyParameters();
|
||||
}
|
||||
|
||||
int JCamera::getRotation() const
|
||||
{
|
||||
return m_rotation;
|
||||
}
|
||||
|
||||
QList<QSize> JCamera::getSupportedPictureSizes()
|
||||
{
|
||||
QList<QSize> list;
|
||||
@@ -655,16 +655,28 @@ void JCamera::setJpegQuality(int quality)
|
||||
applyParameters();
|
||||
}
|
||||
|
||||
void JCamera::requestPreviewFrame()
|
||||
{
|
||||
callMethod<void>("requestPreviewFrame");
|
||||
}
|
||||
|
||||
void JCamera::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()
|
||||
{
|
||||
callMethod<void>("startPreview");
|
||||
@@ -708,8 +720,7 @@ QStringList JCamera::callStringListMethod(const char *methodName)
|
||||
static JNINativeMethod methods[] = {
|
||||
{"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete},
|
||||
{"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed},
|
||||
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured},
|
||||
{"notifyPreviewFrame", "(I[B)V", (void *)notifyPreviewFrame}
|
||||
{"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}
|
||||
};
|
||||
|
||||
bool JCamera::initJNI(JNIEnv *env)
|
||||
|
||||
@@ -82,6 +82,8 @@ public:
|
||||
CameraFacing getFacing();
|
||||
int getNativeOrientation();
|
||||
|
||||
void setDisplayOrientation(int degrees);
|
||||
|
||||
QSize getPreferredPreviewSizeForVideo();
|
||||
QList<QSize> getSupportedPreviewSizes();
|
||||
|
||||
@@ -136,6 +138,7 @@ public:
|
||||
void setWhiteBalance(const QString &value);
|
||||
|
||||
void setRotation(int rotation);
|
||||
int getRotation() const;
|
||||
|
||||
QList<QSize> getSupportedPictureSizes();
|
||||
void setPictureSize(const QSize &size);
|
||||
@@ -144,10 +147,10 @@ public:
|
||||
void startPreview();
|
||||
void stopPreview();
|
||||
|
||||
void requestPreviewFrame();
|
||||
|
||||
void takePicture();
|
||||
|
||||
QByteArray fetchLastPreviewFrame();
|
||||
|
||||
static bool initJNI(JNIEnv *env);
|
||||
|
||||
Q_SIGNALS:
|
||||
@@ -158,8 +161,6 @@ Q_SIGNALS:
|
||||
|
||||
void whiteBalanceChanged();
|
||||
|
||||
void previewFrameAvailable(const QByteArray &data);
|
||||
|
||||
void pictureExposed();
|
||||
void pictureCaptured(const QByteArray &data);
|
||||
|
||||
@@ -174,6 +175,7 @@ private:
|
||||
QJNIObjectPrivate m_parameters;
|
||||
|
||||
QSize m_previewSize;
|
||||
int m_rotation;
|
||||
|
||||
bool m_hasAPI14;
|
||||
};
|
||||
|
||||
@@ -68,339 +68,6 @@
|
||||
#include <wmcodecdsp.h>
|
||||
|
||||
//#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)
|
||||
: m_playerService(playerService)
|
||||
@@ -411,6 +78,8 @@ MFPlayerSession::MFPlayerSession(MFPlayerService *playerService)
|
||||
, m_rateSupport(0)
|
||||
, m_volumeControl(0)
|
||||
, m_netsourceStatistics(0)
|
||||
, m_duration(0)
|
||||
, m_sourceResolver(0)
|
||||
, m_hCloseEvent(0)
|
||||
, m_closing(false)
|
||||
, m_pendingRate(1)
|
||||
@@ -536,7 +205,7 @@ void MFPlayerSession::load(const QMediaContent &media, QIODevice *stream)
|
||||
clear();
|
||||
QMediaResourceList resources = media.resources();
|
||||
|
||||
if (m_status == QMediaPlayer::LoadingMedia)
|
||||
if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver)
|
||||
m_sourceResolver->cancel();
|
||||
|
||||
if (resources.isEmpty() && !stream) {
|
||||
@@ -581,7 +250,7 @@ void MFPlayerSession::handleSourceError(long hr)
|
||||
|
||||
void MFPlayerSession::handleMediaSourceReady()
|
||||
{
|
||||
if (QMediaPlayer::LoadingMedia != m_status)
|
||||
if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver)
|
||||
return;
|
||||
#ifdef DEBUG_MEDIAFOUNDATION
|
||||
qDebug() << "handleMediaSourceReady";
|
||||
@@ -1786,7 +1455,7 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent)
|
||||
{
|
||||
HRESULT hrStatus = S_OK;
|
||||
HRESULT hr = sessionEvent->GetStatus(&hrStatus);
|
||||
if (FAILED(hr)) {
|
||||
if (FAILED(hr) || !m_session) {
|
||||
sessionEvent->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1085,6 +1085,9 @@ namespace
|
||||
HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_shutdown)
|
||||
return MF_E_SHUTDOWN;
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
IUnknown *pState = NULL;
|
||||
hr = pAsyncResult->GetState(&pState);
|
||||
|
||||
@@ -197,7 +197,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream)
|
||||
#ifdef TEST_STREAMING
|
||||
//Testing stream function
|
||||
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)) {
|
||||
m_stream = new MFStream(stream, true);
|
||||
hr = m_sourceResolver->BeginCreateObjectFromByteStream(
|
||||
@@ -217,7 +217,7 @@ void SourceResolver::load(QMediaResourceList& resources, QIODevice* stream)
|
||||
if (url.scheme() == QLatin1String("qrc")) {
|
||||
// If the canonical URL refers to a Qt resource, open with QFile and use
|
||||
// the stream playback capability to play.
|
||||
stream = new QFile(QLatin1Char(':') + url.path(), this);
|
||||
stream = new QFile(QLatin1Char(':') + url.path());
|
||||
if (stream->open(QIODevice::ReadOnly)) {
|
||||
m_stream = new MFStream(stream, true);
|
||||
hr = m_sourceResolver->BeginCreateObjectFromByteStream(
|
||||
|
||||
Reference in New Issue
Block a user