summaryrefslogtreecommitdiffstats
path: root/src/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/android')
-rw-r--r--src/android/CMakeLists.txt3
-rw-r--r--src/android/jar/CMakeLists.txt10
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java146
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java224
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java520
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtCameraListener.java153
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtExifDataHandler.java51
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java44
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java91
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java44
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder.java44
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureListener.java44
-rw-r--r--src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java247
13 files changed, 1128 insertions, 493 deletions
diff --git a/src/android/CMakeLists.txt b/src/android/CMakeLists.txt
index d437e8f0a..7fb5207ba 100644
--- a/src/android/CMakeLists.txt
+++ b/src/android/CMakeLists.txt
@@ -1,3 +1,6 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
# Generated from android.pro.
add_subdirectory(jar)
diff --git a/src/android/jar/CMakeLists.txt b/src/android/jar/CMakeLists.txt
index ed51a1e20..01e0e5a08 100644
--- a/src/android/jar/CMakeLists.txt
+++ b/src/android/jar/CMakeLists.txt
@@ -1,9 +1,15 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
# Generated from jar.pro.
set(java_sources
src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
src/org/qtproject/qt/android/multimedia/QtCameraListener.java
+ src/org/qtproject/qt/android/multimedia/QtCamera2.java
+ src/org/qtproject/qt/android/multimedia/QtExifDataHandler.java
src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
+ src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java
src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java
src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java
@@ -17,8 +23,10 @@ qt_internal_add_jar(Qt${QtMultimedia_VERSION_MAJOR}AndroidMultimedia
OUTPUT_DIR "${QT_BUILD_DIR}/jar"
)
+qt_path_join(destination ${INSTALL_DATADIR} "jar")
+
install_jar(Qt${QtMultimedia_VERSION_MAJOR}AndroidMultimedia
- DESTINATION jar
+ DESTINATION ${destination}
COMPONENT Devel
)
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
index ce5dd5008..9bfad0aa4 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer.java
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
@@ -48,6 +12,7 @@ import java.io.FileInputStream;
import android.content.Context;
import android.media.MediaPlayer;
import android.media.MediaFormat;
+import android.media.PlaybackParams;
import android.media.AudioAttributes;
import android.media.TimedText;
import android.net.Uri;
@@ -62,19 +27,19 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
-public class QtAndroidMediaPlayer
+class QtAndroidMediaPlayer
{
// Native callback functions for MediaPlayer
- native public void onErrorNative(int what, int extra, long id);
- native public void onBufferingUpdateNative(int percent, long id);
- native public void onProgressUpdateNative(int progress, long id);
- native public void onDurationChangedNative(int duration, long id);
- native public void onInfoNative(int what, int extra, long id);
- native public void onVideoSizeChangedNative(int width, int height, long id);
- native public void onStateChangedNative(int state, long id);
+ native void onErrorNative(int what, int extra, long id);
+ native void onBufferingUpdateNative(int percent, long id);
+ native void onProgressUpdateNative(int progress, long id);
+ native void onDurationChangedNative(int duration, long id);
+ native void onInfoNative(int what, int extra, long id);
+ native void onVideoSizeChangedNative(int width, int height, long id);
+ native void onStateChangedNative(int state, long id);
- native public void onTrackInfoChangedNative(long id);
- native public void onTimedTextChangedNative(String text, int time, long id);
+ native void onTrackInfoChangedNative(long id);
+ native void onTimedTextChangedNative(String text, int time, long id);
private MediaPlayer mMediaPlayer = null;
private AudioAttributes mAudioAttributes = null;
@@ -102,7 +67,7 @@ public class QtAndroidMediaPlayer
final static int Error = 0x200;
}
- public class TrackInfo
+ class TrackInfo
{
private int type;
private String mime, language;
@@ -241,19 +206,20 @@ public class QtAndroidMediaPlayer
private class MediaPlayerTimedTextListener implements MediaPlayer.OnTimedTextListener
{
- @Override public void onTimedText(MediaPlayer mp, TimedText text)
+ @Override
+ public void onTimedText(MediaPlayer mp, TimedText text)
{
onTimedTextChangedNative(text.getText(), mp.getCurrentPosition(), mID);
}
}
- public QtAndroidMediaPlayer(final Context context, final long id)
+ QtAndroidMediaPlayer(final Context context, final long id)
{
mID = id;
mContext = context;
}
- public MediaPlayer getMediaPlayerHandle()
+ MediaPlayer getMediaPlayerHandle()
{
return mMediaPlayer;
}
@@ -291,7 +257,7 @@ public class QtAndroidMediaPlayer
mProgressScheduler = Executors.newScheduledThreadPool(1);
}
- public void startProgressWatcher()
+ void startProgressWatcher()
{
// if it was shutdown, request new thread
if (mProgressScheduler.isTerminated() || mProgressScheduler == null)
@@ -306,13 +272,13 @@ public class QtAndroidMediaPlayer
}, 10, 100, TimeUnit.MILLISECONDS);
}
- public void cancelProgressWatcher()
+ void cancelProgressWatcher()
{
if (mProgressScheduler != null)
mProgressScheduler.shutdown();
}
- public void start()
+ void start()
{
if ((mState & (State.Prepared
| State.Started
@@ -330,7 +296,7 @@ public class QtAndroidMediaPlayer
}
}
- public void pause()
+ void pause()
{
if ((mState & (State.Started | State.Paused | State.PlaybackCompleted)) == 0)
return;
@@ -344,7 +310,7 @@ public class QtAndroidMediaPlayer
}
- public void stop()
+ void stop()
{
if ((mState & (State.Prepared
| State.Started
@@ -364,7 +330,7 @@ public class QtAndroidMediaPlayer
}
- public void seekTo(final int msec)
+ void seekTo(final int msec)
{
if ((mState & (State.Prepared
| State.Started
@@ -386,7 +352,7 @@ public class QtAndroidMediaPlayer
}
}
- public boolean isPlaying()
+ boolean isPlaying()
{
boolean playing = false;
if ((mState & (State.Idle
@@ -408,7 +374,7 @@ public class QtAndroidMediaPlayer
return playing;
}
- public void prepareAsync()
+ void prepareAsync()
{
if ((mState & (State.Initialized | State.Stopped)) == 0)
return;
@@ -421,17 +387,17 @@ public class QtAndroidMediaPlayer
}
}
- public void initHeaders()
+ void initHeaders()
{
mHeaders = new HashMap<String, String>();
}
- public void setHeader(final String header, final String value)
+ void setHeader(final String header, final String value)
{
mHeaders.put(header, value);
}
- public void setDataSource(final String path)
+ void setDataSource(final String path)
{
if (mState == State.Uninitialized)
init();
@@ -497,7 +463,7 @@ public class QtAndroidMediaPlayer
return ((mState & preparedState) != 0);
}
- public TrackInfo[] getAllTrackInfo()
+ TrackInfo[] getAllTrackInfo()
{
if (!isMediaPlayerPrepared()) {
Log.w(TAG, "Trying to get track info of a media player that is not prepared!");
@@ -546,7 +512,7 @@ public class QtAndroidMediaPlayer
return mimeType;
}
- public void selectTrack(int index)
+ void selectTrack(int index)
{
if (!isMediaPlayerPrepared()) {
Log.d(TAG, "Trying to select a track of a media player that is not prepared!");
@@ -560,7 +526,7 @@ public class QtAndroidMediaPlayer
}
}
- public void deselectTrack(int index)
+ void deselectTrack(int index)
{
if (!isMediaPlayerPrepared()) {
Log.d(TAG, "Trying to deselect track of a media player that is not prepared!");
@@ -575,7 +541,7 @@ public class QtAndroidMediaPlayer
}
}
- public int getSelectedTrack(int type)
+ int getSelectedTrack(int type)
{
int InvalidTrack = -1;
@@ -606,7 +572,7 @@ public class QtAndroidMediaPlayer
return InvalidTrack;
}
- public int getCurrentPosition()
+ int getCurrentPosition()
{
int currentPosition = 0;
if ((mState & (State.Idle
@@ -629,7 +595,7 @@ public class QtAndroidMediaPlayer
}
- public int getDuration()
+ int getDuration()
{
int duration = 0;
if ((mState & (State.Prepared
@@ -649,7 +615,7 @@ public class QtAndroidMediaPlayer
return duration;
}
- public void setVolume(int volume)
+ void setVolume(int volume)
{
if (volume < 0)
volume = 0;
@@ -683,12 +649,12 @@ public class QtAndroidMediaPlayer
}
}
- public SurfaceHolder display()
+ SurfaceHolder display()
{
return mSurfaceHolder;
}
- public void setDisplay(SurfaceHolder sh)
+ void setDisplay(SurfaceHolder sh)
{
mSurfaceHolder = sh;
@@ -699,23 +665,23 @@ public class QtAndroidMediaPlayer
}
- public int getVolume()
+ int getVolume()
{
return mVolume;
}
- public void mute(final boolean mute)
+ void mute(final boolean mute)
{
mMuted = mute;
setVolumeHelper(mute ? 0 : mVolume);
}
- public boolean isMuted()
+ boolean isMuted()
{
return mMuted;
}
- public void reset()
+ void reset()
{
if (mState == State.Uninitialized) {
return;
@@ -726,7 +692,7 @@ public class QtAndroidMediaPlayer
cancelProgressWatcher();
}
- public void release()
+ void release()
{
if (mMediaPlayer != null) {
mMediaPlayer.reset();
@@ -738,7 +704,7 @@ public class QtAndroidMediaPlayer
cancelProgressWatcher();
}
- public void setAudioAttributes(int type, int usage)
+ void setAudioAttributes(int type, int usage)
{
mAudioAttributes = new AudioAttributes.Builder()
.setUsage(usage)
@@ -759,4 +725,28 @@ public class QtAndroidMediaPlayer
Log.w(TAG, exception);
}
}
+
+ boolean setPlaybackRate(float rate)
+ {
+ PlaybackParams playbackParams = mMediaPlayer.getPlaybackParams();
+ playbackParams.setSpeed(rate);
+ // According to discussion under the patch from QTBUG-61115: At least with DirectShow
+ // and GStreamer, it changes both speed and pitch. (...) need to be consistent
+ if (rate != 0.0)
+ playbackParams.setPitch(Math.abs(rate));
+
+ try {
+ mMediaPlayer.setPlaybackParams(playbackParams);
+ } catch (IllegalStateException | IllegalArgumentException e) {
+ Log.e(TAG, "Cannot set playback rate " + rate + " :" + e.toString());
+ return false;
+ }
+
+ if ((mState & State.Started) == 0 && mMediaPlayer.isPlaying()) {
+ setState(State.Started);
+ startProgressWatcher();
+ }
+
+ return true;
+ }
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
index 245376480..c79e9da31 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtAudioDeviceManager.java
@@ -1,93 +1,74 @@
-/****************************************************************************
-**
-** Copyright (C) 2021 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
import java.util.ArrayList;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHeadset;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
+import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
-public class QtAudioDeviceManager
+class QtAudioDeviceManager
{
+ private static final String TAG = "QtAudioDeviceManager";
static private AudioManager m_audioManager = null;
static private final AudioDevicesReceiver m_audioDevicesReceiver = new AudioDevicesReceiver();
+ static private Handler handler = new Handler(Looper.getMainLooper());
+ static private AudioRecord m_recorder = null;
+ static private AudioTrack m_streamPlayer = null;
+ static private Thread m_streamingThread = null;
+ static private boolean m_isStreaming = false;
+ static private boolean m_useSpeaker = false;
+ static private final int m_sampleRate = 8000;
+ static private final int m_channels = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+ static private final int m_audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ static private final int m_bufferSize = AudioRecord.getMinBufferSize(m_sampleRate, m_channels, m_audioFormat);
- public static native void onAudioInputDevicesUpdated();
- public static native void onAudioOutputDevicesUpdated();
+ static native void onAudioInputDevicesUpdated();
+ static native void onAudioOutputDevicesUpdated();
- static private class AudioDevicesReceiver extends BroadcastReceiver
- {
+ static private void updateDeviceList() {
+ onAudioInputDevicesUpdated();
+ onAudioOutputDevicesUpdated();
+ if (m_useSpeaker) {
+ final AudioDeviceInfo[] audioDevices =
+ m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ setAudioOutput(getModeForSpeaker(audioDevices), false, true);
+ }
+ }
+
+ private static class AudioDevicesReceiver extends AudioDeviceCallback {
@Override
- public void onReceive(Context context, Intent intent) {
- onAudioInputDevicesUpdated();
- onAudioOutputDevicesUpdated();
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ updateDeviceList();
}
+
+ @Override
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ updateDeviceList();
+ }
+ }
+
+
+ static void registerAudioHeadsetStateReceiver()
+ {
+ m_audioManager.registerAudioDeviceCallback(m_audioDevicesReceiver, handler);
}
- public static void registerAudioHeadsetStateReceiver(Context context)
+ static void unregisterAudioHeadsetStateReceiver()
{
- IntentFilter audioDevicesFilter = new IntentFilter();
- audioDevicesFilter.addAction(AudioManager.ACTION_HEADSET_PLUG);
- audioDevicesFilter.addAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
- audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
- audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
- audioDevicesFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
- audioDevicesFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- audioDevicesFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- audioDevicesFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
- audioDevicesFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
- audioDevicesFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- audioDevicesFilter.addAction(BluetoothA2dp.ACTION_PLAYING_STATE_CHANGED);
-
- context.registerReceiver(m_audioDevicesReceiver, audioDevicesFilter);
+ m_audioManager.unregisterAudioDeviceCallback(m_audioDevicesReceiver);
}
- static public void setContext(Context context)
+ static void setContext(Context context)
{
m_audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
}
@@ -150,6 +131,7 @@ public class QtAudioDeviceManager
private static String audioDeviceTypeToString(int type)
{
+ // API <= 23 types
switch (type)
{
case AudioDeviceInfo.TYPE_AUX_LINE:
@@ -187,11 +169,16 @@ public class QtAudioDeviceManager
return "Wired headphones";
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
return "Wired headset";
- case AudioDeviceInfo.TYPE_TELEPHONY:
- case AudioDeviceInfo.TYPE_UNKNOWN:
- default:
- return "Unknown-Type";
}
+
+ // API 24
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ if (type == AudioDeviceInfo.TYPE_BUS)
+ return "Bus";
+ }
+
+ return "Unknown-Type";
+
}
private static String[] getAudioDevices(int type)
@@ -238,8 +225,27 @@ public class QtAudioDeviceManager
return ret;
}
+ private static int getModeForSpeaker(AudioDeviceInfo[] audioDevices)
+ {
+ // If we want to force device to use speaker when Bluetooth or Wiread headset is connected,
+ // we need to use MODE_IN_COMMUNICATION. Otherwise the MODE_NORMAL can be used.
+ for (AudioDeviceInfo deviceInfo : audioDevices) {
+ switch (deviceInfo.getType()) {
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ case AudioDeviceInfo.TYPE_BLUETOOTH_SCO:
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ return AudioManager.MODE_IN_COMMUNICATION;
+ default: break;
+ }
+ }
+ return AudioManager.MODE_NORMAL;
+ }
+
+
private static boolean setAudioOutput(int id)
{
+ m_useSpeaker = false;
final AudioDeviceInfo[] audioDevices =
m_audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo deviceInfo : audioDevices) {
@@ -251,7 +257,8 @@ public class QtAudioDeviceManager
setAudioOutput(AudioManager.MODE_IN_COMMUNICATION, true, false);
return true;
case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER:
- setAudioOutput(AudioManager.MODE_NORMAL, false, true);
+ m_useSpeaker = true;
+ setAudioOutput(getModeForSpeaker(audioDevices), false, true);
return true;
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
@@ -261,8 +268,15 @@ public class QtAudioDeviceManager
// It doesn't work when WIRED HEADPHONES are connected
// Earpiece has the lowest priority and setWiredHeadsetOn(boolean)
// method to force it is deprecated
+ Log.w(TAG, "Built in Earpiece may not work when "
+ + "Wired Headphones are connected");
setAudioOutput(AudioManager.MODE_IN_CALL, false, false);
return true;
+ case AudioDeviceInfo.TYPE_HDMI:
+ case AudioDeviceInfo.TYPE_HDMI_ARC:
+ case AudioDeviceInfo.TYPE_HDMI_EARC:
+ setAudioOutput(AudioManager.MODE_NORMAL, false, false);
+ return true;
default:
return false;
}
@@ -283,4 +297,66 @@ public class QtAudioDeviceManager
m_audioManager.setSpeakerphoneOn(speakerOn);
}
+
+ private static void streamSound()
+ {
+ byte data[] = new byte[m_bufferSize];
+ while (m_isStreaming) {
+ m_recorder.read(data, 0, m_bufferSize);
+ m_streamPlayer.play();
+ m_streamPlayer.write(data, 0, m_bufferSize);
+ m_streamPlayer.stop();
+ }
+ }
+
+ private static void startSoundStreaming(int inputId, int outputId)
+ {
+ if (m_isStreaming)
+ stopSoundStreaming();
+
+ m_recorder = new AudioRecord(MediaRecorder.AudioSource.DEFAULT, m_sampleRate, m_channels,
+ m_audioFormat, m_bufferSize);
+ m_streamPlayer = new AudioTrack(AudioManager.STREAM_MUSIC, m_sampleRate, m_channels,
+ m_audioFormat, m_bufferSize, AudioTrack.MODE_STREAM);
+
+ final AudioDeviceInfo[] devices = m_audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+ for (AudioDeviceInfo deviceInfo : devices) {
+ if (deviceInfo.getId() == outputId) {
+ m_streamPlayer.setPreferredDevice(deviceInfo);
+ } else if (deviceInfo.getId() == inputId) {
+ m_recorder.setPreferredDevice(deviceInfo);
+ }
+ }
+
+ m_recorder.startRecording();
+ m_isStreaming = true;
+
+ m_streamingThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ streamSound();
+ }
+ });
+
+ m_streamingThread.start();
+ }
+
+ private static void stopSoundStreaming()
+ {
+ if (!m_isStreaming)
+ return;
+
+ m_isStreaming = false;
+ try {
+ m_streamingThread.join();
+ m_streamingThread = null;
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ m_recorder.stop();
+ m_recorder.release();
+ m_streamPlayer.release();
+ m_streamPlayer = null;
+ m_recorder = null;
+ }
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java
new file mode 100644
index 000000000..f74fc1e6e
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCamera2.java
@@ -0,0 +1,520 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+package org.qtproject.qt.android.multimedia;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Range;
+import android.view.Surface;
+import java.lang.Thread;
+import java.util.ArrayList;
+import java.util.List;
+
+@TargetApi(23)
+class QtCamera2 {
+
+ CameraDevice mCameraDevice = null;
+ QtVideoDeviceManager mVideoDeviceManager = null;
+ HandlerThread mBackgroundThread;
+ Handler mBackgroundHandler;
+ ImageReader mImageReader = null;
+ ImageReader mCapturedPhotoReader = null;
+ CameraManager mCameraManager;
+ CameraCaptureSession mCaptureSession;
+ CaptureRequest.Builder mPreviewRequestBuilder;
+ CaptureRequest mPreviewRequest;
+ String mCameraId;
+ List<Surface> mTargetSurfaces = new ArrayList<>();
+
+ private static final int STATE_PREVIEW = 0;
+ private static final int STATE_WAITING_LOCK = 1;
+ private static final int STATE_WAITING_PRECAPTURE = 2;
+ private static final int STATE_WAITING_NON_PRECAPTURE = 3;
+ private static final int STATE_PICTURE_TAKEN = 4;
+
+ private int mState = STATE_PREVIEW;
+ private Object mStartMutex = new Object();
+ private boolean mIsStarted = false;
+ private static int MaxNumberFrames = 12;
+ private int mFlashMode = CaptureRequest.CONTROL_AE_MODE_ON;
+ private int mTorchMode = CameraMetadata.FLASH_MODE_OFF;
+ private int mAFMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+ private float mZoomFactor = 1.0f;
+ private Range<Integer> mFpsRange = null;
+ private QtExifDataHandler mExifDataHandler = null;
+
+ native void onCameraOpened(String cameraId);
+ native void onCameraDisconnect(String cameraId);
+ native void onCameraError(String cameraId, int error);
+ CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
+ @Override
+ public void onOpened(CameraDevice cameraDevice) {
+ if (mCameraDevice != null)
+ mCameraDevice.close();
+ mCameraDevice = cameraDevice;
+ onCameraOpened(mCameraId);
+ }
+ @Override
+ public void onDisconnected(CameraDevice cameraDevice) {
+ cameraDevice.close();
+ if (mCameraDevice == cameraDevice)
+ mCameraDevice = null;
+ onCameraDisconnect(mCameraId);
+ }
+ @Override
+ public void onError(CameraDevice cameraDevice, int error) {
+ cameraDevice.close();
+ if (mCameraDevice == cameraDevice)
+ mCameraDevice = null;
+ onCameraError(mCameraId, error);
+ }
+ };
+
+ native void onCaptureSessionConfigured(String cameraId);
+ native void onCaptureSessionConfigureFailed(String cameraId);
+ CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
+ @Override
+ public void onConfigured(CameraCaptureSession cameraCaptureSession) {
+ mCaptureSession = cameraCaptureSession;
+ onCaptureSessionConfigured(mCameraId);
+ }
+
+ @Override
+ public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
+ onCaptureSessionConfigureFailed(mCameraId);
+ }
+
+ @Override
+ public void onActive(CameraCaptureSession cameraCaptureSession) {
+ super.onActive(cameraCaptureSession);
+ onSessionActive(mCameraId);
+ }
+
+ @Override
+ public void onClosed(CameraCaptureSession cameraCaptureSession) {
+ super.onClosed(cameraCaptureSession);
+ onSessionClosed(mCameraId);
+ }
+ };
+
+ native void onSessionActive(String cameraId);
+ native void onSessionClosed(String cameraId);
+ native void onCaptureSessionFailed(String cameraId, int reason, long frameNumber);
+ CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
+ super.onCaptureFailed(session, request, failure);
+ onCaptureSessionFailed(mCameraId, failure.getReason(), failure.getFrameNumber());
+ }
+
+ private void process(CaptureResult result) {
+ switch (mState) {
+ case STATE_WAITING_LOCK: {
+ Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+ if (afState == null) {
+ capturePhoto();
+ } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
+ CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null ||
+ aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ mState = STATE_PICTURE_TAKEN;
+ capturePhoto();
+ } else {
+ try {
+ mPreviewRequestBuilder.set(
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+ mState = STATE_WAITING_PRECAPTURE;
+ mCaptureSession.capture(mPreviewRequestBuilder.build(),
+ mCaptureCallback,
+ mBackgroundHandler);
+ } catch (CameraAccessException e) {
+ Log.w("QtCamera2", "Cannot get access to the camera: " + e);
+ }
+ }
+ }
+ break;
+ }
+ case STATE_WAITING_PRECAPTURE: {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+ mState = STATE_WAITING_NON_PRECAPTURE;
+ }
+ break;
+ }
+ case STATE_WAITING_NON_PRECAPTURE: {
+ Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+ if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+ mState = STATE_PICTURE_TAKEN;
+ capturePhoto();
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onCaptureProgressed(CameraCaptureSession s, CaptureRequest r, CaptureResult partialResult) {
+ process(partialResult);
+ }
+
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession s, CaptureRequest r, TotalCaptureResult result) {
+ process(result);
+ }
+ };
+
+ QtCamera2(Context context) {
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ mVideoDeviceManager = new QtVideoDeviceManager(context);
+ startBackgroundThread();
+ }
+
+ void startBackgroundThread() {
+ mBackgroundThread = new HandlerThread("CameraBackground");
+ mBackgroundThread.start();
+ mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
+ }
+
+ void stopBackgroundThread() {
+ mBackgroundThread.quitSafely();
+ try {
+ mBackgroundThread.join();
+ mBackgroundThread = null;
+ mBackgroundHandler = null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ boolean open(String cameraId) {
+ try {
+ mCameraId = cameraId;
+ mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
+ return true;
+ } catch (Exception e){
+ Log.w("QtCamera2", "Failed to open camera:" + e);
+ }
+
+ return false;
+ }
+
+ native void onPhotoAvailable(String cameraId, Image frame);
+
+ ImageReader.OnImageAvailableListener mOnPhotoAvailableListener = new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ QtCamera2.this.onPhotoAvailable(mCameraId, reader.acquireLatestImage());
+ }
+ };
+
+ native void onFrameAvailable(String cameraId, Image frame);
+
+ ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
+ @Override
+ public void onImageAvailable(ImageReader reader) {
+ try {
+ Image img = reader.acquireLatestImage();
+ if (img != null)
+ QtCamera2.this.onFrameAvailable(mCameraId, img);
+ } catch (IllegalStateException e) {
+ // It seems that ffmpeg is processing images for too long (and does not close it)
+ // Give it a little more time. Restarting the camera session if it doesn't help
+ Log.e("QtCamera2", "Image processing taking too long. Let's wait 0,5s more " + e);
+ try {
+ Thread.sleep(500);
+ QtCamera2.this.onFrameAvailable(mCameraId, reader.acquireLatestImage());
+ } catch (IllegalStateException | InterruptedException e2) {
+ Log.e("QtCamera2", "Will not wait anymore. Restart camera session. " + e2);
+ // Remember current used camera ID, because stopAndClose will clear the value
+ String cameraId = mCameraId;
+ stopAndClose();
+ addImageReader(mImageReader.getWidth(), mImageReader.getHeight(),
+ mImageReader.getImageFormat());
+ open(cameraId);
+ }
+ }
+ }
+ };
+
+
+ void prepareCamera(int width, int height, int format, int minFps, int maxFps) {
+
+ addImageReader(width, height, format);
+ setFrameRate(minFps, maxFps);
+ }
+
+ private void addImageReader(int width, int height, int format) {
+
+ if (mImageReader != null)
+ removeSurface(mImageReader.getSurface());
+
+ if (mCapturedPhotoReader != null)
+ removeSurface(mCapturedPhotoReader.getSurface());
+
+ mImageReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
+ mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
+ addSurface(mImageReader.getSurface());
+
+ mCapturedPhotoReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
+ mCapturedPhotoReader.setOnImageAvailableListener(mOnPhotoAvailableListener, mBackgroundHandler);
+ addSurface(mCapturedPhotoReader.getSurface());
+ }
+
+ private void setFrameRate(int minFrameRate, int maxFrameRate) {
+
+ if (minFrameRate <= 0 || maxFrameRate <= 0)
+ mFpsRange = null;
+ else
+ mFpsRange = new Range<>(minFrameRate, maxFrameRate);
+ }
+
+ boolean addSurface(Surface surface) {
+ if (mTargetSurfaces.contains(surface))
+ return true;
+
+ return mTargetSurfaces.add(surface);
+ }
+
+ boolean removeSurface(Surface surface) {
+ return mTargetSurfaces.remove(surface);
+ }
+
+ void clearSurfaces() {
+ mTargetSurfaces.clear();
+ }
+
+ boolean createSession() {
+ if (mCameraDevice == null)
+ return false;
+
+ try {
+ mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
+ return true;
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to create a capture session:" + exception);
+ }
+ return false;
+ }
+
+ boolean start(int template) {
+
+ if (mCameraDevice == null)
+ return false;
+
+ if (mCaptureSession == null)
+ return false;
+
+ synchronized (mStartMutex) {
+ try {
+ mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(template);
+ mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
+ mAFMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+ for (int mode : mVideoDeviceManager.getSupportedAfModes(mCameraId)) {
+ if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+ mAFMode = mode;
+ break;
+ }
+ }
+
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
+ mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mTorchMode);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mAFMode);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
+ if (mZoomFactor != 1.0f)
+ mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
+ if (mFpsRange != null)
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mFpsRange);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
+ mIsStarted = true;
+ return true;
+
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to start preview:" + exception);
+ }
+ return false;
+ }
+ }
+
+ void stopAndClose() {
+ synchronized (mStartMutex) {
+ try {
+ if (null != mCaptureSession) {
+ mCaptureSession.close();
+ mCaptureSession = null;
+ }
+ if (null != mCameraDevice) {
+ mCameraDevice.close();
+ mCameraDevice = null;
+ }
+ mCameraId = "";
+ mTargetSurfaces.clear();
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to stop and close:" + exception);
+ }
+ mIsStarted = false;
+ }
+ }
+
+ private void capturePhoto() {
+ try {
+ final CaptureRequest.Builder captureBuilder =
+ mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+ captureBuilder.addTarget(mCapturedPhotoReader.getSurface());
+ captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
+ if (mZoomFactor != 1.0f)
+ captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
+
+ CameraCaptureSession.CaptureCallback captureCallback
+ = new CameraCaptureSession.CaptureCallback() {
+ @Override
+ public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+ TotalCaptureResult result) {
+ try {
+ mExifDataHandler = new QtExifDataHandler(result);
+ // Reset the focus/flash and go back to the normal state of preview.
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+ mState = STATE_PREVIEW;
+ mCaptureSession.setRepeatingRequest(mPreviewRequest,
+ mCaptureCallback,
+ mBackgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);
+ } catch (CameraAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ void takePhoto() {
+ try {
+ if (mAFMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
+ mState = STATE_WAITING_LOCK;
+ mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
+ } else {
+ capturePhoto();
+ }
+ } catch (CameraAccessException e) {
+ Log.w("QtCamera2", "Cannot get access to the camera: " + e);
+ }
+ }
+
+ void saveExifToFile(String path)
+ {
+ if (mExifDataHandler != null)
+ mExifDataHandler.save(path);
+ else
+ Log.e("QtCamera2", "No Exif data that could be saved to " + path);
+ }
+
+ private Rect getScalerCropRegion()
+ {
+ Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
+ float zoomRatio = 1.0f;
+ if (mZoomFactor != 0.0f)
+ zoomRatio = 1.0f/mZoomFactor;
+ int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
+ int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
+ return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
+ activePixels.height() - croppedHeight/2);
+ }
+
+ void zoomTo(float factor)
+ {
+ synchronized (mStartMutex) {
+ mZoomFactor = factor;
+
+ if (!mIsStarted) {
+ Log.w("QtCamera2", "Cannot set zoom on invalid camera");
+ return;
+ }
+
+ mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
+ mPreviewRequest = mPreviewRequestBuilder.build();
+
+ try {
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to set zoom:" + exception);
+ }
+ }
+ }
+ void setFlashMode(String flashMode)
+ {
+ synchronized (mStartMutex) {
+
+ int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
+ if (flashModeValue < 0) {
+ Log.w("QtCamera2", "Unknown flash mode");
+ return;
+ }
+ mFlashMode = flashModeValue;
+
+ if (!mIsStarted)
+ return;
+
+ mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+
+ try {
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to set flash mode:" + exception);
+ }
+ }
+ }
+
+ private int getTorchModeValue(boolean mode)
+ {
+ return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
+ }
+
+ void setTorchMode(boolean torchMode)
+ {
+ synchronized (mStartMutex) {
+ mTorchMode = getTorchModeValue(torchMode);
+
+ if (mIsStarted) {
+ mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mTorchMode);
+ mPreviewRequest = mPreviewRequestBuilder.build();
+
+ try {
+ mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
+ } catch (Exception exception) {
+ Log.w("QtCamera2", "Failed to set flash mode:" + exception);
+ }
+ }
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCameraListener.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCameraListener.java
index 7f5361e77..23e9a3580 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCameraListener.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtCameraListener.java
@@ -1,61 +1,21 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
import android.hardware.Camera;
import android.hardware.Camera.CameraInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
+import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.util.Log;
-import java.io.File;
-import java.io.FileOutputStream;
import java.lang.Math;
-import android.media.ExifInterface;
import java.io.ByteArrayOutputStream;
-import java.lang.String;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
-public class QtCameraListener implements Camera.ShutterCallback,
+class QtCameraListener implements Camera.ShutterCallback,
Camera.PictureCallback,
Camera.AutoFocusCallback,
Camera.PreviewCallback
@@ -73,28 +33,29 @@ public class QtCameraListener implements Camera.ShutterCallback,
private Camera.Size m_previewSize = null;
private int m_previewFormat = ImageFormat.NV21; // Default preview format on all devices
private int m_previewBytesPerLine = -1;
+ private int m_rotation = 0;
private QtCameraListener(int id)
{
m_cameraId = id;
}
- public void notifyNewFrames(boolean notify)
+ void notifyNewFrames(boolean notify)
{
m_notifyNewFrames = notify;
}
- public void notifyWhenFrameAvailable(boolean notify)
+ void notifyWhenFrameAvailable(boolean notify)
{
m_notifyWhenFrameAvailable = notify;
}
- public byte[] lastPreviewBuffer()
+ byte[] lastPreviewBuffer()
{
return m_lastPreviewBuffer;
}
- public int previewWidth()
+ int previewWidth()
{
if (m_previewSize == null)
return -1;
@@ -102,7 +63,7 @@ public class QtCameraListener implements Camera.ShutterCallback,
return m_previewSize.width;
}
- public int previewHeight()
+ int previewHeight()
{
if (m_previewSize == null)
return -1;
@@ -110,22 +71,27 @@ public class QtCameraListener implements Camera.ShutterCallback,
return m_previewSize.height;
}
- public int previewFormat()
+ int previewFormat()
{
return m_previewFormat;
}
- public int previewBytesPerLine()
+ int previewBytesPerLine()
{
return m_previewBytesPerLine;
}
- public void clearPreviewCallback(Camera camera)
+ void clearPreviewCallback(Camera camera)
{
camera.setPreviewCallbackWithBuffer(null);
}
- public void setupPreviewCallback(Camera camera)
+ void setPhotoRotation(int rotation)
+ {
+ m_rotation = rotation;
+ }
+
+ void setupPreviewCallback(Camera camera)
{
// Clear previous callback (also clears added buffers)
clearPreviewCallback(camera);
@@ -208,69 +174,22 @@ public class QtCameraListener implements Camera.ShutterCallback,
@Override
public void onPictureTaken(byte[] data, Camera camera)
{
- File outputFile = null;
- try {
- outputFile = File.createTempFile("pic_", ".jpg", QtMultimediaUtils.getCacheDirectory());
- FileOutputStream out = new FileOutputStream(outputFile);
-
- // we just want to read the exif...
- BitmapFactory.decodeByteArray(data, 0, data.length)
- .compress(Bitmap.CompressFormat.JPEG, 10, out);
-
- ExifInterface exif = new ExifInterface(outputFile.getAbsolutePath());
- int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
- ExifInterface.ORIENTATION_UNDEFINED);
-
- int degree = 0;
-
- switch (orientation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- degree = 90;
- break;
- case ExifInterface.ORIENTATION_ROTATE_180:
- degree = 180;
- break;
- case ExifInterface.ORIENTATION_ROTATE_270:
- degree = 270;
- break;
- }
-
- Camera.CameraInfo info = new Camera.CameraInfo();
- Camera.getCameraInfo(m_cameraId, info);
-
- int rotation = (info.orientation - degree + 360) % 360;
-
- Bitmap source = BitmapFactory.decodeByteArray(data, 0, data.length);
- Matrix matrix = new Matrix();
- matrix.postRotate(rotation);
-
- if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
- matrix.postScale(-1, 1, source.getWidth() / 2.0f, source.getHeight() / 2.0f);
- }
-
- Bitmap rotatedBitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(),
- source.getHeight(), matrix, true);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
- byte[] byteArray = outputStream.toByteArray();
-
- rotatedBitmap.recycle();
- source.recycle();
-
- notifyPictureCaptured(m_cameraId, byteArray);
-
- return;
-
- } catch (Exception e) {
- Log.w(TAG, "Error fixing bitmap orientation.");
- e.printStackTrace();
- } finally {
- if (outputFile != null && outputFile.exists())
- outputFile.delete();
+ Camera.CameraInfo info = new Camera.CameraInfo();
+ Camera.getCameraInfo(m_cameraId, info);
+ Bitmap source = BitmapFactory.decodeByteArray(data, 0, data.length);
+ Matrix matrix = new Matrix();
+ matrix.postRotate(m_rotation);
+ if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+ matrix.postScale(-1, 1, source.getWidth() / 2.0f, source.getHeight() / 2.0f);
}
-
- notifyPictureCaptured(m_cameraId, data);
+ Bitmap rotatedBitmap = Bitmap.createBitmap(source, 0, 0, source.getWidth(),
+ source.getHeight(), matrix, true);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ rotatedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+ byte[] byteArray = outputStream.toByteArray();
+ rotatedBitmap.recycle();
+ source.recycle();
+ notifyPictureCaptured(m_cameraId, byteArray);
}
@Override
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtExifDataHandler.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtExifDataHandler.java
new file mode 100644
index 000000000..a27d98967
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtExifDataHandler.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+package org.qtproject.qt.android.multimedia;
+
+import android.hardware.camera2.CaptureResult;
+import android.media.ExifInterface;
+import android.os.Build;
+import android.util.Log;
+
+import java.io.IOException;
+
+class QtExifDataHandler {
+
+ private int mFlashFired = 0;
+ private long mExposureTime = 0L;
+ private float mFocalLength = 0;
+ private static String mModel = Build.MANUFACTURER + " " + Build.MODEL;
+
+ QtExifDataHandler(CaptureResult r)
+ {
+ Integer flash = r.get(CaptureResult.FLASH_STATE);
+ if (flash != null && flash == CaptureResult.FLASH_STATE_FIRED)
+ mFlashFired = 1;
+
+ Long exposureTime = r.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+ if (exposureTime != null)
+ mExposureTime = exposureTime/1000000000;
+ mFocalLength = r.get(CaptureResult.LENS_FOCAL_LENGTH);
+ }
+
+ void save(String path)
+ {
+ ExifInterface exif;
+ try {
+ exif = new ExifInterface(path);
+ } catch ( IOException e ) {
+ Log.e("QtExifDataHandler", "Cannot open file: " + path + "\n" + e);
+ return;
+ }
+ exif.setAttribute(ExifInterface.TAG_FLASH, String.valueOf(mFlashFired));
+ exif.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, String.valueOf(mExposureTime));
+ exif.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, String.valueOf(mFocalLength));
+ exif.setAttribute(ExifInterface.TAG_MODEL, mModel);
+
+ try {
+ exif.saveAttributes();
+ } catch ( IOException e ) {
+ Log.e("QtExifDataHandler", "Cannot save file: " + path + "\n" + e);
+ }
+ }
+}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java
index bf1763dee..3cf77c323 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMediaRecorderListener.java
@@ -1,51 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
import android.media.MediaRecorder;
-public class QtMediaRecorderListener implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener
+class QtMediaRecorderListener implements MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener
{
private long m_id = -1;
- public QtMediaRecorderListener(long id)
+ QtMediaRecorderListener(long id)
{
m_id = id;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java
index f2c6113b4..21a3989a6 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtMultimediaUtils.java
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
@@ -53,13 +17,13 @@ import java.lang.String;
import java.io.File;
import android.util.Log;
-public class QtMultimediaUtils
+class QtMultimediaUtils
{
static private class OrientationListener extends OrientationEventListener
{
- static public int deviceOrientation = 0;
+ static int deviceOrientation = 0;
- public OrientationListener(Context context)
+ OrientationListener(Context context)
{
super(context);
}
@@ -78,17 +42,17 @@ public class QtMultimediaUtils
static private OrientationListener m_orientationListener = null;
private static final String QtTAG = "Qt QtMultimediaUtils";
- static public void setActivity(Activity qtMainActivity, Object qtActivityDelegate)
+ static void setActivity(Activity qtMainActivity, Object qtActivityDelegate)
{
}
- static public void setContext(Context context)
+ static void setContext(Context context)
{
m_context = context;
m_orientationListener = new OrientationListener(context);
}
- public QtMultimediaUtils()
+ QtMultimediaUtils()
{
}
@@ -102,7 +66,7 @@ public class QtMultimediaUtils
static int getDeviceOrientation()
{
- return m_orientationListener.deviceOrientation;
+ return OrientationListener.deviceOrientation;
}
static String getDefaultMediaDirectory(int type)
@@ -161,24 +125,25 @@ public class QtMultimediaUtils
return codecs;
}
-public static String getMimeType(Context context, String url)
-{
- Uri parsedUri = Uri.parse(url);
- String type = null;
-
- try {
- String scheme = parsedUri.getScheme();
- if (scheme != null && scheme.contains("content")) {
- ContentResolver cR = context.getContentResolver();
- type = cR.getType(parsedUri);
- } else {
- String extension = MimeTypeMap.getFileExtensionFromUrl(url);
- if (extension != null)
+ static String getMimeType(Context context, String url)
+ {
+ Uri parsedUri = Uri.parse(url);
+ String type = null;
+
+ try {
+ String scheme = parsedUri.getScheme();
+ if (scheme != null && scheme.contains("content")) {
+ ContentResolver cR = context.getContentResolver();
+ type = cR.getType(parsedUri);
+ } else {
+ String extension = MimeTypeMap.getFileExtensionFromUrl(url);
+ if (extension != null)
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
- }
- } catch (Exception e) {
- Log.e(QtTAG, "getMimeType(): " + e.toString());
+ }
+ } catch (Exception e) {
+ Log.e(QtTAG, "getMimeType(): " + e.toString());
+ }
+ return type;
}
- return type;
-}
}
+
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java
index 62000716b..bbaa0d5b9 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback.java
@@ -1,51 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
import android.view.SurfaceHolder;
-public class QtSurfaceHolderCallback implements SurfaceHolder.Callback
+class QtSurfaceHolderCallback implements SurfaceHolder.Callback
{
private long m_id = -1;
- public QtSurfaceHolderCallback(long id)
+ QtSurfaceHolderCallback(long id)
{
m_id = id;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder.java
index ea7a41505..345c313e2 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder.java
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the (whatever) of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
@@ -44,11 +8,11 @@ import android.view.Surface;
import android.graphics.Rect;
import android.graphics.Canvas;
-public class QtSurfaceTextureHolder implements SurfaceHolder
+class QtSurfaceTextureHolder implements SurfaceHolder
{
private Surface surfaceTexture;
- public QtSurfaceTextureHolder(Surface surface)
+ QtSurfaceTextureHolder(Surface surface)
{
surfaceTexture = surface;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureListener.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureListener.java
index 4d929c6ad..9b4180b5d 100644
--- a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureListener.java
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtSurfaceTextureListener.java
@@ -1,51 +1,15 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtMultimedia of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
package org.qtproject.qt.android.multimedia;
import android.graphics.SurfaceTexture;
-public class QtSurfaceTextureListener implements SurfaceTexture.OnFrameAvailableListener
+class QtSurfaceTextureListener implements SurfaceTexture.OnFrameAvailableListener
{
private final long m_id;
- public QtSurfaceTextureListener(long id)
+ QtSurfaceTextureListener(long id)
{
m_id = id;
}
diff --git a/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
new file mode 100644
index 000000000..6ec2073d8
--- /dev/null
+++ b/src/android/jar/src/org/qtproject/qt/android/multimedia/QtVideoDeviceManager.java
@@ -0,0 +1,247 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+package org.qtproject.qt.android.multimedia;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.os.Build;
+import android.util.Range;
+import android.util.Size;
+import android.util.Log;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+class QtVideoDeviceManager {
+
+ CameraManager mCameraManager;
+ Map<String, CameraCharacteristics> cache;
+
+ QtVideoDeviceManager(Context context) {
+ mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+ cache = new WeakHashMap<String, CameraCharacteristics>();
+ }
+
+ CameraCharacteristics getCameraCharacteristics(String cameraId) {
+
+ if (cache.containsKey(cameraId))
+ return cache.get(cameraId);
+
+ try {
+ CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
+ cache.put(cameraId, cameraCharacteristics);
+ return cameraCharacteristics;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ static private boolean isSoftwareCodec(String longCodecName) {
+ longCodecName = longCodecName.toLowerCase();
+ return longCodecName.startsWith("omx.google.") || longCodecName.startsWith("c2.android.")
+ || !(longCodecName.startsWith("omx.") || longCodecName.startsWith("c2."));
+ }
+
+ private enum CODEC {
+ DECODER,
+ ENCODER
+ }
+ static private String[] getHWVideoCodecs(CODEC expectedType) {
+ MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaCodecInfo[] mediaCodecInfo = mediaCodecList.getCodecInfos();
+ Set<String> codecs = new HashSet<String>();
+
+ for (MediaCodecInfo codecInfo : mediaCodecInfo) {
+ CODEC currentType = codecInfo.isEncoder() ? CODEC.ENCODER : CODEC.DECODER;
+ if (currentType == expectedType && !isSoftwareCodec(codecInfo.getName())) {
+ String[] supportedTypes = codecInfo.getSupportedTypes();
+ for (String type : supportedTypes) {
+ if (type.startsWith("video/"))
+ codecs.add(type.substring(6));
+ }
+ }
+ }
+ return codecs.toArray(new String[codecs.size()]);
+ }
+
+ static String[] getHWVideoDecoders() { return getHWVideoCodecs(CODEC.DECODER); }
+ static String[] getHWVideoEncoders() { return getHWVideoCodecs(CODEC.ENCODER); }
+
+ String[] getCameraIdList() {
+ try {
+ return mCameraManager.getCameraIdList();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ int getSensorOrientation(String cameraId) {
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return 0;
+ return characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+ }
+
+ int getLensFacing(String cameraId) {
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return 0;
+ return characteristics.get(CameraCharacteristics.LENS_FACING);
+ }
+
+ String[] getFpsRange(String cameraId) {
+
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return new String[0];
+
+ Range<Integer>[] ranges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+
+ String[] fps = new String[ranges.length];
+
+ for (int index = 0; index < ranges.length; index++) {
+ fps[index] = ranges[index].toString();
+ }
+
+ return fps;
+ }
+
+ float getMaxZoom(String cameraId) {
+
+ float maxZoom = 1.0f;
+ final CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics != null)
+ maxZoom = characteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+ return maxZoom;
+ }
+
+ Rect getActiveArraySize(String cameraId) {
+ Rect activeArraySize = new Rect();
+ final CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics != null)
+ activeArraySize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ return activeArraySize;
+ }
+
+ static final int maxResolution = 3840*2160; // 4k resolution
+ String[] getStreamConfigurationsSizes(String cameraId, int imageFormat) {
+
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return new String[0];
+
+ StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+ Size[] sizes = map.getOutputSizes(imageFormat);
+ if (sizes == null)
+ return new String[0];
+
+ ArrayList<String> stream = new ArrayList<>();
+
+ for (int index = 0; index < sizes.length; index++) {
+ if (sizes[index].getWidth() * sizes[index].getHeight() <= maxResolution)
+ stream.add(sizes[index].toString());
+ }
+
+ return stream.toArray(new String[0]);
+ }
+
+ int stringToControlAEMode(String mode) {
+ switch (mode) {
+ case "off":
+ return CaptureRequest.CONTROL_AE_MODE_ON;
+ case "auto":
+ return CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH;
+ case "on":
+ return CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
+ case "redeye":
+ return CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
+ case "external":
+ return CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH;
+ default:
+ return -1;
+ }
+ }
+
+ String controlAEModeToString(int mode) {
+ switch (mode) {
+ case CaptureRequest.CONTROL_AE_MODE_ON:
+ return "off";
+ case CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH:
+ return "auto";
+ case CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH:
+ return "on";
+ case CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE:
+ return "redeye";
+ case CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH:
+ return "external";
+ case CaptureRequest.CONTROL_AE_MODE_OFF:
+ default:
+ return "unknown";
+ }
+ }
+
+ int[] getSupportedAfModes(String cameraId) {
+
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return new int[0];
+
+ return characteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES);
+ }
+
+ String[] getSupportedFlashModes(String cameraId) {
+
+ CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics == null)
+ return new String[0];
+
+ int supportedFlashModes[] = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
+ ArrayList<String> supportedFlashModesList = new ArrayList<String>();
+ for (int index = 0; index < supportedFlashModes.length; index++) {
+ supportedFlashModesList.add(controlAEModeToString(supportedFlashModes[index]));
+ }
+
+ String[] ret = new String[ supportedFlashModesList.size() ];
+ return supportedFlashModesList.toArray(ret);
+ }
+
+ static boolean isEmulator()
+ {
+ return ((Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
+ || Build.FINGERPRINT.startsWith("generic")
+ || Build.FINGERPRINT.startsWith("unknown")
+ || Build.HARDWARE.contains("goldfish")
+ || Build.HARDWARE.contains("ranchu")
+ || Build.MODEL.contains("google_sdk")
+ || Build.MODEL.contains("Emulator")
+ || Build.MODEL.contains("Android SDK built for x86")
+ || Build.MANUFACTURER.contains("Genymotion")
+ || Build.PRODUCT.contains("sdk")
+ || Build.PRODUCT.contains("vbox86p")
+ || Build.PRODUCT.contains("emulator")
+ || Build.PRODUCT.contains("simulator"));
+ }
+
+ boolean isTorchModeSupported(String cameraId) {
+ boolean ret = false;
+ final CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
+ if (characteristics != null)
+ ret = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ return ret;
+ }
+}