From 6001157eddcd8454ba4284ba0ab264813df520d1 Mon Sep 17 00:00:00 2001 From: MRSa Date: Fri, 1 Jan 2021 22:22:16 +0900 Subject: [PATCH] =?utf8?q?=E3=82=B9=E3=83=A2=E3=83=BC=E3=83=AB=E7=94=BB?= =?utf8?q?=E5=83=8F=E3=82=92=E5=8F=96=E5=BE=97=E3=81=99=E3=82=8B=E3=82=B7?= =?utf8?q?=E3=83=BC=E3=82=B1=E3=83=B3=E3=82=B9=E3=82=92=E8=A4=87=E6=95=B0?= =?utf8?q?=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../nikon/wrapper/NikonInterfaceProvider.java | 6 +- .../ptpip/wrapper/PtpIpInterfaceProvider.java | 5 +- .../ptpip/wrapper/command/IPtpIpCommand.java | 13 + .../wrapper/command/IPtpIpCommandPublisher.java | 4 + .../ptpip/wrapper/command/PtpIpCommandPublisher.kt | 762 +++++++++++++++++++++ ...dPublisher.java => PtpIpCommandPublisher0.java} | 38 +- .../wrapper/command/messages/PtpIpCommandBase.java | 27 +- .../wrapper/playback/CanonPlaybackControl.java | 42 +- .../wrapper/playback/CanonReducedImageReceiver.kt | 217 ++++++ .../wrapper/playback/CanonSmallImageReceiver.java | 3 +- .../wrapper/playback/ICanonSmallImageReceiver.java | 8 + .../preference/IPreferencePropertyAccessor.java | 3 + .../preference/canon/CanonPreferenceFragment.java | 3 + .../preference/fujix/FujiXPreferenceFragment.java | 3 + .../preference/nikon/NikonPreferenceFragment.java | 3 + .../preference/olympus/OpcPreferenceFragment.java | 3 + .../olympuspen/OlympusPenPreferenceFragment.java | 3 + .../panasonic/PanasonicPreferenceFragment.java | 3 + .../pixpro/PixproPreferenceFragment.java | 3 + .../ricohgr2/RicohGr2PreferenceFragment.java | 3 + .../preference/sony/SonyPreferenceFragment.java | 3 + .../preference/theta/ThetaPreferenceFragment.java | 3 + app/src/main/res/values-ja/strings.xml | 3 + app/src/main/res/values/arrays.xml | 10 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/xml/preferences_canon.xml | 8 + 26 files changed, 1153 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.kt rename app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/{PtpIpCommandPublisher.java => PtpIpCommandPublisher0.java} (96%) create mode 100644 app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonReducedImageReceiver.kt create mode 100644 app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/ICanonSmallImageReceiver.java diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/nikon/wrapper/NikonInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/nikon/wrapper/NikonInterfaceProvider.java index aba6c5d..d4ecb6a 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/nikon/wrapper/NikonInterfaceProvider.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/nikon/wrapper/NikonInterfaceProvider.java @@ -38,7 +38,7 @@ import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpComma import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpCommandPublisher; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpCommunication; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpAsyncResponseReceiver; -import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpCommandPublisher; +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpCommandPublisher0; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.connection.NikonConnection; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.liveview.PtpIpLiveViewControl; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.status.IPtpIpRunModeHolder; @@ -61,7 +61,7 @@ public class NikonInterfaceProvider implements INikonInterfaceProvider, IDisplay private final PtpIpHardwareStatus hardwareStatus; private PtpIpButtonControl ptpIpButtonControl; private NikonConnection nikonConnection; - private PtpIpCommandPublisher commandPublisher; + private PtpIpCommandPublisher0 commandPublisher; private PtpIpLiveViewControl liveViewControl; private PtpIpAsyncResponseReceiver asyncReceiver; private PtpIpZoomControl zoomControl; @@ -85,7 +85,7 @@ public class NikonInterfaceProvider implements INikonInterfaceProvider, IDisplay { e.printStackTrace(); } - commandPublisher = new PtpIpCommandPublisher(ipAddress, CONTROL_PORT); + commandPublisher = new PtpIpCommandPublisher0(ipAddress, CONTROL_PORT); liveViewControl = new PtpIpLiveViewControl(context, ipAddress, STREAM_PORT); asyncReceiver = new PtpIpAsyncResponseReceiver(ipAddress, ASYNC_RESPONSE_PORT); statusChecker = new NikonStatusChecker(activity, commandPublisher, ipAddress, EVENT_PORT); diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/PtpIpInterfaceProvider.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/PtpIpInterfaceProvider.java index bc08cf8..539e639 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/PtpIpInterfaceProvider.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/PtpIpInterfaceProvider.java @@ -34,6 +34,7 @@ import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpComma import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpCommunication; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpAsyncResponseReceiver; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpCommandPublisher; +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.PtpIpCommandPublisher0; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.connection.CanonConnection; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.liveview.PtpIpLiveViewControl; import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.playback.CanonPlaybackControl; @@ -57,6 +58,7 @@ public class PtpIpInterfaceProvider implements IPtpIpInterfaceProvider, IDisplay private final PtpIpHardwareStatus hardwareStatus; private final PtpIpButtonControl ptpIpButtonControl; private final CanonConnection canonConnection; + //private final PtpIpCommandPublisher0 commandPublisher; private final PtpIpCommandPublisher commandPublisher; private final PtpIpLiveViewControl liveViewControl; private final PtpIpAsyncResponseReceiver asyncReceiver; @@ -100,7 +102,8 @@ public class PtpIpInterfaceProvider implements IPtpIpInterfaceProvider, IDisplay ipAddress = "192.168.0.1"; } - commandPublisher = new PtpIpCommandPublisher(ipAddress, CONTROL_PORT); + commandPublisher = new PtpIpCommandPublisher(ipAddress, CONTROL_PORT, false, false); + //commandPublisher = new PtpIpCommandPublisher0(ipAddress, CONTROL_PORT); liveViewControl = new PtpIpLiveViewControl(context, ipAddress, STREAM_PORT); asyncReceiver = new PtpIpAsyncResponseReceiver(ipAddress, ASYNC_RESPONSE_PORT); statusChecker = new PtpIpStatusChecker(context, commandPublisher, ipAddress, EVENT_PORT); diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommand.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommand.java index e2f6383..86761c2 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommand.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommand.java @@ -52,4 +52,17 @@ public interface IPtpIpCommand // デバッグ用: ログ(logcat)に通信結果を残すかどうか boolean dumpLog(); + + // リトライオーバー発生時、コマンドを再送するか? + boolean isRetrySend(); + + // 最後に1回余計に受信をするか? + boolean isLastReceiveRetry(); + + // 受信待ち再試行回数 + int maxRetryCount(); + + // リトライオーバーで再送するとき、SeqNoをインクリメントするか + boolean isIncrementSequenceNumberToRetry(); + } diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommandPublisher.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommandPublisher.java index dcf33b7..bd01a13 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommandPublisher.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/IPtpIpCommandPublisher.java @@ -9,6 +9,10 @@ public interface IPtpIpCommandPublisher boolean flushHoldQueue(); + int isExistCommandMessageQueue(int id); + int getCurrentQueueSize(); + boolean flushQueue(); + void start(); void stop(); } diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.kt new file mode 100644 index 0000000..b181035 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.kt @@ -0,0 +1,762 @@ +package net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command + +import android.util.Log +import net.osdn.gokigen.pkremote.camera.utils.SimpleLogDumper +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.DataOutputStream +import java.io.InputStream +import java.net.InetSocketAddress +import java.net.Socket +import java.util.* + +class PtpIpCommandPublisher(private val ipAddress : String, private val portNumber : Int, private val tcpNoDelay : Boolean, private val waitForever: Boolean) : IPtpIpCommandPublisher, IPtpIpCommunication +{ + private var isConnected = false + private var isStart = false + private var isHold = false + private var holdId = 0 + + private var socket : Socket? = null + private var dos: DataOutputStream? = null + private var bufferedReader: BufferedReader? = null + + private var sequenceNumber = SEQUENCE_START_NUMBER + private var commandQueue: Queue = ArrayDeque() + private var holdCommandQueue: Queue = ArrayDeque() + + init + { + commandQueue.clear() + holdCommandQueue.clear() + } + + override fun isConnected(): Boolean + { + return (isConnected) + } + + override fun connect(): Boolean + { + try + { + Log.v(TAG, " connect()") + socket = Socket() + socket?.tcpNoDelay = tcpNoDelay + if (tcpNoDelay) + { + //socket?.tcpNoDelay = true + //socket?.keepAlive = false + socket?.keepAlive = false + //socket?.setPerformancePreferences(0, 1, 2) + socket?.setPerformancePreferences(0, 1, 2) + //socket?.setPerformancePreferences(0, 1, 2) + //socket?.setPerformancePreferences(1, 0, 0) + //socket?.setPerformancePreferences(0, 0, 2) + socket?.oobInline = true + //socket?.reuseAddress = false + socket?.trafficClass = 0x80 // 0x80 + socket?.soTimeout = 0 + //socket?.soTimeout = 0 + //socket?.receiveBufferSize = 8192 // 10240 // 16384 // 6144// 8192 // 49152 // 65536 // 32768 + //socket?.sendBufferSize = 1024 // 8192 // 4096 // 2048 // 10240 + socket?.setSoLinger(true, 500) + //socket?.setReceiveBufferSize(2097152); + //socket?.setSendBufferSize(524288); + + Log.v(TAG, " SOCKET (SEND:${socket?.sendBufferSize}, RECV:${socket?.receiveBufferSize}) oob:${socket?.oobInline} SO_TIMEOUT:${socket?.soTimeout}ms trafficClass:${socket?.trafficClass}") + } + socket?.connect(InetSocketAddress(ipAddress, portNumber), 0) + isConnected = true + } + catch (e: Exception) + { + e.printStackTrace() + isConnected = false + socket = null + } + return (isConnected) + } + + override fun disconnect() + { + try + { + dos?.close() + bufferedReader?.close() + socket?.close() + commandQueue.clear() + } + catch (e : Exception) + { + e.printStackTrace() + } + System.gc() + + sequenceNumber = SEQUENCE_START_NUMBER + isConnected = false + isStart = false + dos = null + bufferedReader = null + socket = null + } + + override fun start() + { + if (isStart) + { + // すでにコマンドのスレッド動作中なので抜ける + return + } + if (socket == null) + { + isStart = false + Log.v(TAG, " SOCKET IS NULL. (cannot start)") + return + } + + isStart = true + Log.v(TAG, " start()") + val thread = Thread { + try + { + dos = DataOutputStream(socket?.getOutputStream()) + while (isStart) + { + try + { + val command = commandQueue.poll() + command?.let { issueCommand(it) } + Thread.sleep(COMMAND_POLL_QUEUE_MS.toLong()) + } + catch (e: Exception) + { + e.printStackTrace() + } + } + } + catch (e: Exception) + { + Log.v(TAG, "<<<<< IP : $ipAddress port : $portNumber >>>>>") + e.printStackTrace() + } + } + try + { + thread.start() + } + catch (e: Exception) + { + e.printStackTrace() + } + } + + override fun stop() + { + isStart = false + commandQueue.clear() + } + + override fun enqueueCommand(command: IPtpIpCommand): Boolean + { + try + { + if (isHold) + { + return (if (holdId == command.holdId) { + if (command.isRelease) + { + // コマンドをキューに積んだ後、リリースする + val ret = commandQueue.offer(command) + isHold = false + + // 溜まっているキューを積みなおす + while (holdCommandQueue.size != 0) + { + val queuedCommand = holdCommandQueue.poll() + commandQueue.offer(queuedCommand) + if (queuedCommand != null && queuedCommand.isHold) + { + // 特定シーケンスに入った場合は、そこで積みなおすのをやめる + isHold = true + holdId = queuedCommand.holdId + break + } + } + return ret + } + commandQueue.offer(command) + } + else + { + // 特定シーケンスではなかったので HOLD + holdCommandQueue.offer(command) + }) + } + if (command.isHold) + { + isHold = true + holdId = command.holdId + } + if (commandQueue.size > 1) + { + // たまっているときだけログを吐く + Log.v(TAG, "Enqueue [ID: " + command.id + "] size: " + commandQueue.size) + } + return (commandQueue.offer(command)) + } + catch (e: Exception) + { + e.printStackTrace() + } + return (false) + } + + override fun getCurrentQueueSize(): Int + { + return commandQueue.size + } + + override fun flushQueue(): Boolean + { + Log.v(TAG, " flushQueue() size: ${commandQueue.size}") + // TODO: たまっているキューをダンプする + commandQueue.clear() + System.gc() + return (true) + } + + override fun isExistCommandMessageQueue(id: Int): Int + { + var count = 0 + for (cmd in commandQueue) + { + if (cmd.id == id) + { + count++ + } + } + return count + } + + override fun flushHoldQueue(): Boolean + { + Log.v(TAG, " flushHoldQueue()") + holdCommandQueue.clear() + System.gc() + return (true) + } + + private fun issueCommand(command: IPtpIpCommand) + { + try + { + var retryOver = true + while (retryOver) + { + //Log.v(TAG, "issueCommand : " + command.getId()); + val commandBody = command.commandBody() + if (commandBody != null) + { + // コマンドボディが入っていた場合には、コマンド送信(入っていない場合は受信待ち) + sendToCamera(command.dumpLog(), commandBody, command.useSequenceNumber(), command.embeddedSequenceNumberIndex()) + val commandBody2 = command.commandBody2() + if (commandBody2 != null) + { + // コマンドボディの2つめが入っていた場合には、コマンドを連続送信する + sendToCamera(command.dumpLog(), commandBody2, command.useSequenceNumber(), command.embeddedSequenceNumberIndex2()) + } + val commandBody3 = command.commandBody3() + if (commandBody3 != null) + { + // コマンドボディの3つめが入っていた場合には、コマンドを連続送信する + sendToCamera(command.dumpLog(), commandBody3, command.useSequenceNumber(), command.embeddedSequenceNumberIndex3()) + } + if (command.isIncrementSeqNumber) + { + // シーケンス番号を更新する + sequenceNumber++ + } + } + retryOver = receiveFromCamera(command) + if ((retryOver)&&(commandBody != null)) + { + if (!command.isRetrySend) + { + while (retryOver) + { + // コマンドを再送信しない場合はここで応答を待つ... + retryOver = receiveFromCamera(command) + } + break + } + if (!command.isIncrementSequenceNumberToRetry) + { + // 再送信...のために、シーケンス番号を戻す... + sequenceNumber-- + } + } + } + } + catch (e: Exception) + { + e.printStackTrace() + } + } + + /** + * カメラにコマンドを送信する(メイン部分) + * + */ + private fun sendToCamera(isDumpReceiveLog: Boolean, byte_array: ByteArray, useSequenceNumber: Boolean, embeddedSequenceIndex: Int) + { + try + { + if (dos == null) + { + Log.v(TAG, " DataOutputStream is null.") + return + } + + // メッセージボディを加工: 最初に4バイトのレングス長をつける + val sendData = ByteArray(byte_array.size + 4) + sendData[0] = (byte_array.size + 4).toByte() + sendData[1] = 0x00 + sendData[2] = 0x00 + sendData[3] = 0x00 + System.arraycopy(byte_array, 0, sendData, 4, byte_array.size) + if (useSequenceNumber) + { + // Sequence Number を反映させる + sendData[embeddedSequenceIndex ] = (0x000000ff and sequenceNumber).toByte() + sendData[embeddedSequenceIndex + 1] = (0x0000ff00 and sequenceNumber ushr 8 and 0x000000ff).toByte() + sendData[embeddedSequenceIndex + 2] = (0x00ff0000 and sequenceNumber ushr 16 and 0x000000ff).toByte() + sendData[embeddedSequenceIndex + 3] = (-0x1000000 and sequenceNumber ushr 24 and 0x000000ff).toByte() + if (isDumpReceiveLog) + { + Log.v(TAG, "----- SEQ No. : $sequenceNumber -----") + } + } + if (isDumpReceiveLog) + { + // ログに送信メッセージを出力する + SimpleLogDumper.dump_bytes("SEND[" + sendData.size + "] ", sendData) + } + + // (データを)送信 + dos?.write(sendData) + dos?.flush() + } + catch (e: Exception) + { + e.printStackTrace() + } + } + + private fun sleep(delayMs: Int) + { + try + { + Thread.sleep(delayMs.toLong()) + } + catch (e: Exception) + { + e.printStackTrace() + } + } + + /** + * カメラからにコマンドの結果を受信する(メイン部分) + * + */ + private fun receiveFromCamera(command: IPtpIpCommand): Boolean + { + val callback = command.responseCallback() + var delayMs = command.receiveDelayMs() + if (delayMs < 0 || delayMs > COMMAND_SEND_RECEIVE_DURATION_MAX) + { + delayMs = COMMAND_SEND_RECEIVE_DURATION_MS + } + + return (if (callback != null && callback.isReceiveMulti) + { + // 受信したら逐次「受信したよ」と応答するパターン + //Log.v(TAG, " receiveMulti() : $delayMs [id:${command.id}] SEQ: $sequenceNumber") + receiveMulti(command, delayMs) + } + else + { + //Log.v(TAG, " receiveSingle() : $delayMs [id:${command.id}] SEQ: $sequenceNumber") + receiveSingle(command, delayMs) + }) + // 受信した後、すべてをまとめて「受信したよ」と応答するパターン + } + + private fun receiveSingle(command: IPtpIpCommand, delayMs: Int): Boolean + { + val isDumpReceiveLog = command.dumpLog() + val id = command.id + val callback = command.responseCallback() + try + { + val receiveMessageBufferSize = BUFFER_SIZE + val byteArray = ByteArray(receiveMessageBufferSize) + val inputStream = socket?.getInputStream() + if (inputStream == null) + { + Log.v(TAG, " InputStream is NULL... RECEIVE ABORTED.") + receivedAllMessage(isDumpReceiveLog, id, null, callback) + return (false) + } + + // 初回データが受信バッファにデータが溜まるまで待つ... + var readBytes = waitForReceive(inputStream, delayMs, command.maxRetryCount()) + if (readBytes <= 0) + { + // リトライオーバー... + Log.v(TAG, " RECEIVE : RETRY OVER...... : $delayMs ms x ${command.maxRetryCount()} SEQ: $sequenceNumber isRetry: ${command.isRetrySend}") + if (!command.isRetrySend) + { + // 再送しない場合には、応答がないことを通知する + receivedAllMessage(isDumpReceiveLog, id, null, callback) + return (false) + } + return (true) + } + + // 受信したデータをバッファに突っ込む + var receivedLength = 0 + val byteStream = ByteArrayOutputStream() + while (readBytes > 0) + { + readBytes = inputStream.read(byteArray, 0, receiveMessageBufferSize) + if (readBytes <= 0) + { + Log.v(TAG, " RECEIVED MESSAGE FINISHED ($readBytes)") + break + } + byteStream.write(byteArray, 0, readBytes) + sleep(delayMs) + receivedLength += readBytes + readBytes = inputStream.available() + } + + Log.v(TAG, " receivedLength : $receivedLength") + if (receivedLength >= 4) + { + val outputStream = cutHeader(byteStream) + receivedAllMessage(isDumpReceiveLog, id, outputStream.toByteArray(), callback) + } + else + { + receivedAllMessage(isDumpReceiveLog, id, byteStream.toByteArray(), callback) + } + System.gc() + } + catch (e: Throwable) + { + e.printStackTrace() + System.gc() + } + return false + } + + private fun receivedAllMessage(isDumpReceiveLog: Boolean, id: Int, body: ByteArray?, callback: IPtpIpCommandCallback?) + { + Log.v(TAG, "receivedAllMessage() : " + (body?.size ?: 0) + " bytes.") + if ((isDumpReceiveLog)&&(body != null)) + { + // ログに受信メッセージを出力する + SimpleLogDumper.dump_bytes("RECV[" + body.size + "] ", body) + } + callback?.receivedMessage(id, body) + } + + private fun receiveMulti(command: IPtpIpCommand, delayMs: Int): Boolean + { + val isDumpLog = command.dumpLog() + var maxRetryCount = command.maxRetryCount() + val id = command.id + val callback = command.responseCallback() + try + { + // Log.v(TAG, " ===== receive_multi() =====") + val receiveMessageBufferSize = BUFFER_SIZE + val byteArray = ByteArray(receiveMessageBufferSize) + val inputStream = socket?.getInputStream() + if (inputStream == null) + { + Log.v(TAG, " InputStream is NULL... RECEIVE ABORTED.") + return (false) + } + + // 初回データが受信バッファにデータが溜まるまで待つ... + var readBytes = waitForReceive(inputStream, delayMs, command.maxRetryCount()) + if (readBytes <= 0) + { + // リトライオーバー... + Log.v(TAG, " RECEIVE : RETRY OVER...... : $delayMs ms x ${command.maxRetryCount()} SEQ: $sequenceNumber ") + if (command.isRetrySend) + { + // 要求を再送する場合、、、ダメな場合は受信待ちとする + Log.v(TAG, " --- SEND RETRY ---") + return (true) + } + callback?.receivedMessage(id, null) + return (false) + } + + // 初回データの読み込み... + var targetLength = parseDataLength(byteArray, readBytes) + readBytes = inputStream.read(byteArray, 0, receiveMessageBufferSize) + var receivedLength = readBytes + if (targetLength <= 0) + { + // もう一回データを読み直す... + targetLength = parseDataLength(byteArray, readBytes) + } + if ((targetLength == 0)&&(readBytes > 0)) + { + // 知らないデータがついている...ダンプしてみる + // Log.v(TAG, " RECEIVE UNKNOWN BYTES : ${readBytes}") + if (isDumpLog) + { + // ログに送信メッセージを出力する + SimpleLogDumper.dump_bytes("RECV.UNKNOWN[${readBytes}] ", byteArray) + } + callback?.receivedMessage(id, null) + return (false) + } + + if ((targetLength <= 0)||(readBytes <= 0)) + { + // 受信サイズ異常の場合... + if (isDumpLog) + { + if (receivedLength > 0) + { + SimpleLogDumper.dump_bytes("WRONG DATA : ", byteArray.copyOfRange(0, Math.min(receivedLength, 64))) + } + Log.v(TAG, " WRONG LENGTH. : $targetLength READ : $receivedLength bytes.") + } + callback?.receivedMessage(id, null) + + // 受信したデータが不足しているので、もう一度受信待ち + Log.v(TAG, " 1st receive : AGAIN. [$readBytes][$targetLength]") + return (true) + } + + // 初回データの受信を報告する。 + if (isDumpLog) + { + Log.v(TAG, " -=-=-=- 1st CALL : read_bytes : " + readBytes + "(" + receivedLength + ") : target_length : " + targetLength + " buffer SIZE : " + byteArray.size) + } + callback?.onReceiveProgress(receivedLength, targetLength, byteArray.copyOfRange(fromIndex = 0, toIndex = receivedLength)) + + var isWaitLogging = true + while ((maxRetryCount > 0)&&(readBytes >= 0)&&(receivedLength < targetLength)) + { + sleep(delayMs) + readBytes = inputStream.available() + if (readBytes <= 0) + { + if (isWaitLogging) + { + Log.v(TAG, " WAIT is.available() ... [length: $receivedLength, target: $targetLength] $readBytes bytes, retry : $maxRetryCount SEQ: $sequenceNumber") + isWaitLogging = false + } + maxRetryCount-- + continue + } + if (!isWaitLogging) + { + Log.v(TAG, " WAIT FOR RECEIVE COUNT: $maxRetryCount (SEQ: $sequenceNumber)") + } + + readBytes = inputStream.read(byteArray, 0, receiveMessageBufferSize) + if (readBytes <= 0) + { + if (isDumpLog) + { + Log.v(TAG, " RECEIVED MESSAGE FINISHED ($readBytes) [receivedLength: $receivedLength, targetLength: $targetLength]") + } + break + } + receivedLength += readBytes + callback?.onReceiveProgress(receivedLength, targetLength, byteArray.copyOfRange(0, readBytes)) + maxRetryCount = command.maxRetryCount() + } + + // 最後のデータを受信した後にもう一度受信が必要な場合の処理... + if (command.isLastReceiveRetry) + { + var responseReceive = true + try + { + while (responseReceive) + { + Log.v(TAG, " --- isLastReceiveRetry is true --- SEQ: $sequenceNumber") + sleep(delayMs) + if (inputStream.available() > 0) + { + readBytes = inputStream.read(byteArray, 0, receiveMessageBufferSize) + if (readBytes > 0) + { + receivedLength += readBytes + callback?.onReceiveProgress(receivedLength, targetLength, byteArray.copyOfRange(0, readBytes)) + Log.v(TAG, " --- isLastReceiveRetry: onReceiveProgress() $readBytes bytes. --- SEQ: $sequenceNumber ") + } + responseReceive = false + } + else + { + Log.v(TAG, " --- inputStream.available() is <= 0 --- : ${inputStream.available()} SEQ: $sequenceNumber") + responseReceive = false + } + } + } + catch (ex: Exception) + { + ex.printStackTrace() + } + } + + // 終了報告... + if (isDumpLog) + { + Log.v(TAG, " --- receive_multi : $id ($readBytes) [$maxRetryCount] $receiveMessageBufferSize ($receivedLength) SEQ: $sequenceNumber") + } + val copyBytes = if (byteArray.size > receivedLength) { receivedLength } else { byteArray.size } + callback?.receivedMessage(id, byteArray.copyOfRange(0, copyBytes)) + } + catch (e: Throwable) + { + e.printStackTrace() + } + return (false) + } + + private fun parseDataLength(byte_array: ByteArray, read_bytes: Int): Int + { + var offset = 0 + var lenlen = 0 + try + { + if (read_bytes > 20) + { + if (byte_array[offset + 4].toUByte().toInt() == 0x07) + { + // 前の応答が入っていると考える... レングスバイト分読み飛ばす + offset = byte_array[offset].toUByte().toInt() + } + if (byte_array[offset + 4].toUByte().toInt() == 0x09) + { + // データバイト... + lenlen = (byte_array[offset + 15].toUByte().toInt() and 0xff shl 24) + (byte_array[offset + 14].toUByte().toInt() and 0xff shl 16) + (byte_array[offset + 13].toUByte().toInt() and 0xff shl 8) + (byte_array[offset + 12].toUByte().toInt() and 0xff) + } + } + } + catch (e: Exception) + { + e.printStackTrace() + } + return (lenlen) + } + + private fun cutHeader(receivedBuffer: ByteArrayOutputStream): ByteArrayOutputStream + { + try + { + val byteArray = receivedBuffer.toByteArray() + val limit = byteArray.size + var lenlen = 0 + val len = (byteArray[3].toUByte().toInt() and 0xff shl 24) + (byteArray[2].toUByte().toInt() and 0xff shl 16) + (byteArray[1].toUByte().toInt() and 0xff shl 8) + (byteArray[0].toUByte().toInt() and 0xff) + val packetType = byteArray[4].toUByte().toInt() and 0xff + if ((limit == len)||(limit < 16384)) + { + // 応答は1つしか入っていない。もしくは受信データサイズが16kBの場合は、そのまま返す。 + return (receivedBuffer) + } + + if (packetType == 0x09) + { + lenlen = (byteArray[15].toUByte().toInt() and 0xff shl 24) + (byteArray[14].toUByte().toInt() and 0xff shl 16) + (byteArray[13].toUByte().toInt() and 0xff shl 8) + (byteArray[12].toUByte().toInt() and 0xff) + } + + if (lenlen == 0) + { + // データとしては変なので、なにもしない + return receivedBuffer + } + val outputStream = ByteArrayOutputStream() + var position = 20 // ヘッダ込の先頭 + while (position < limit) + { + lenlen = (byteArray[position + 3].toUByte().toInt() and 0xff shl 24) + (byteArray[position + 2].toUByte().toInt() and 0xff shl 16) + (byteArray[position + 1].toUByte().toInt() and 0xff shl 8) + (byteArray[position].toUByte().toInt() and 0xff) + + val copyByte = Math.min(limit - (position + 12), lenlen - 12) + outputStream.write(byteArray, position + 12, copyByte) + position += lenlen + } + return (outputStream) + } + catch (e: Throwable) + { + e.printStackTrace() + System.gc() + } + return (receivedBuffer) + } + + private fun waitForReceive(inputStream : InputStream, delayMs: Int, retryCnt : Int): Int + { + var retryCount = retryCnt + var isLogOutput = true + var readBytes = 0 + try + { + while ((retryCount >= 0) && (readBytes <= 0)) + { + sleep(delayMs) + readBytes = inputStream.available() + if (readBytes <= 0) // if (readBytes <= 0) + { + if (isLogOutput) + { + Log.v(TAG, "waitForReceive:: is.available() WAIT... : $delayMs ms (Count : $retryCount/$retryCnt) SEQ: $sequenceNumber") + isLogOutput = false + } + if (!waitForever) + { + retryCount-- + } + else + { + Log.v(TAG, "waitForReceive: wait forever ") + isLogOutput = false + } + } + } + if (!isLogOutput) + { + Log.v(TAG, " --- waitForReceive : $readBytes bytes. (RetryCount : $retryCount/$retryCnt)") + } + } + catch (e: Exception) + { + e.printStackTrace() + } + return (readBytes) + } + + companion object + { + private val TAG = PtpIpCommandPublisher::class.java.simpleName + + private const val SEQUENCE_START_NUMBER = 1 + private const val BUFFER_SIZE = 1024 * 1024 + 16 // 受信バッファは 1MB + private const val COMMAND_SEND_RECEIVE_DURATION_MS = 5 + private const val COMMAND_SEND_RECEIVE_DURATION_MAX = 3000 + private const val COMMAND_POLL_QUEUE_MS = 5 // 5 + } +} diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher0.java similarity index 96% rename from app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.java rename to app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher0.java index 2a381c3..7a09616 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/PtpIpCommandPublisher0.java @@ -15,9 +15,9 @@ import java.util.Queue; import static net.osdn.gokigen.pkremote.camera.utils.SimpleLogDumper.dump_bytes; -public class PtpIpCommandPublisher implements IPtpIpCommandPublisher, IPtpIpCommunication +public class PtpIpCommandPublisher0 implements IPtpIpCommandPublisher, IPtpIpCommunication { - private static final String TAG = PtpIpCommandPublisher.class.getSimpleName(); + private static final String TAG = PtpIpCommandPublisher0.class.getSimpleName(); private static final int SEQUENCE_START_NUMBER = 1; private static final int BUFFER_SIZE = 1024 * 1024 + 16; // 受信バッファは 256kB @@ -35,10 +35,10 @@ public class PtpIpCommandPublisher implements IPtpIpCommandPublisher, IPtpIpComm private DataOutputStream dos = null; private BufferedReader bufferedReader = null; private int sequenceNumber = SEQUENCE_START_NUMBER; - private Queue commandQueue; - private Queue holdCommandQueue; + private final Queue commandQueue; + private final Queue holdCommandQueue; - public PtpIpCommandPublisher(@NonNull String ip, int portNumber) + public PtpIpCommandPublisher0(@NonNull String ip, int portNumber) { this.ipAddress = ip; this.portNumber = portNumber; @@ -233,6 +233,34 @@ public class PtpIpCommandPublisher implements IPtpIpCommandPublisher, IPtpIpComm return (true); } + @Override + public int isExistCommandMessageQueue(int id) + { + int count = 0; + for (IPtpIpCommand cmd : commandQueue) + { + if (cmd.getId() == id) + { + count++; + } + } + return (count); + } + + @Override + public int getCurrentQueueSize() + { + return (commandQueue.size()); + } + + @Override + public boolean flushQueue() + { + commandQueue.clear(); + System.gc(); + return (true); + } + private void issueCommand(@NonNull IPtpIpCommand command) { try diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/messages/PtpIpCommandBase.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/messages/PtpIpCommandBase.java index a25c881..feac3f0 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/messages/PtpIpCommandBase.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/command/messages/PtpIpCommandBase.java @@ -34,7 +34,7 @@ public class PtpIpCommandBase implements IPtpIpCommand, IPtpIpMessages @Override public int receiveDelayMs() { - return (15); + return (30); } @Override @@ -108,4 +108,29 @@ public class PtpIpCommandBase implements IPtpIpCommand, IPtpIpMessages { return (true); } + + @Override + public boolean isRetrySend() + { + return (false); + } + + @Override + public boolean isLastReceiveRetry() + { + return (false); + } + + @Override + public int maxRetryCount() + { + return (3500); + } + + @Override + public boolean isIncrementSequenceNumberToRetry() + { + return (false); + } + } diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonPlaybackControl.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonPlaybackControl.java index 774710b..f19658a 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonPlaybackControl.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonPlaybackControl.java @@ -32,20 +32,20 @@ public class CanonPlaybackControl implements IPlaybackControl private final Activity activity; private final PtpIpInterfaceProvider provider; private final CanonFullImageReceiver fullImageReceiver; - private final CanonSmallImageReceiver smallImageReciever; - //private int delayMs = 20; + private final ICanonSmallImageReceiver smallImageReciever; private String raw_suffix = "CR2"; - private boolean use_screennail_image = false; - private CanonImageObjectReceiver canonImageObjectReceiver; + private boolean useScreennailImage = false; + private final CanonImageObjectReceiver canonImageObjectReceiver; public CanonPlaybackControl(Activity activity, PtpIpInterfaceProvider provider) { + int smallImageSequence = 0; int delayMs = 20; try { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); raw_suffix = preferences.getString(IPreferencePropertyAccessor.CANON_RAW_SUFFIX, IPreferencePropertyAccessor.CANON_RAW_SUFFIX_DEFAULT_VALUE); - use_screennail_image = preferences.getBoolean(IPreferencePropertyAccessor.CANON_USE_SCREENNAIL_AS_SMALL, false); + useScreennailImage = preferences.getBoolean(IPreferencePropertyAccessor.CANON_USE_SCREENNAIL_AS_SMALL, false); try { delayMs = Integer.parseInt(preferences.getString(IPreferencePropertyAccessor.CANON_RECEIVE_WAIT, IPreferencePropertyAccessor.CANON_RECEIVE_WAIT_DEFAULT_VALUE)); @@ -58,6 +58,14 @@ public class CanonPlaybackControl implements IPlaybackControl { delayMs = 10; // 最短は 10msにする } + try + { + smallImageSequence = Integer.parseInt(preferences.getString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE)); + } + catch (Exception ee) + { + ee.printStackTrace(); + } } catch (Exception e) { @@ -66,9 +74,15 @@ public class CanonPlaybackControl implements IPlaybackControl this.activity = activity; this.provider = provider; this.fullImageReceiver = new CanonFullImageReceiver(activity, provider.getCommandPublisher()); - this.smallImageReciever = new CanonSmallImageReceiver(activity, provider.getCommandPublisher()); + if (smallImageSequence == 1) + { + this.smallImageReciever = new CanonReducedImageReceiver(activity, provider.getCommandPublisher()); + } + else + { + this.smallImageReciever = new CanonSmallImageReceiver(activity, provider.getCommandPublisher()); + } canonImageObjectReceiver = new CanonImageObjectReceiver(provider, delayMs); - } @Override @@ -101,7 +115,7 @@ public class CanonPlaybackControl implements IPlaybackControl { Log.v(TAG, " downloadContentScreennail() " + path); - if (!use_screennail_image) + if (!useScreennailImage) { // Thumbnail と同じ画像を表示する downloadContentThumbnail(path, callback); @@ -152,17 +166,13 @@ public class CanonPlaybackControl implements IPlaybackControl { start = 1; } - //String indexStr = path.substring(start, path.indexOf(".")); - final String indexStr = path.substring(start); - //Log.v(TAG, "downloadContentThumbnail() : [" + path + "] " + indexStr); + final String indexStr = path.substring(start); CanonImageContentInfo content = canonImageObjectReceiver.getContentObject(indexStr); if (content != null) { - IPtpIpCommandPublisher publisher = provider.getCommandPublisher(); - //int storageId = content.getStorageId(); int objectId = content.getId(); - // Log.v(TAG, "downloadContentThumbnail() " + indexStr + " [" + objectId + "] (" + storageId + ")"); + IPtpIpCommandPublisher publisher = provider.getCommandPublisher(); publisher.enqueueCommand(new PtpIpCommandGeneric(new PtpIpThumbnailImageReceiver(activity, callback), objectId, false, 0, 0x910a, 8, objectId, 0x00032000)); } } @@ -211,7 +221,6 @@ public class CanonPlaybackControl implements IPlaybackControl { return; } - try { Thread thread = new Thread(new Runnable() { @@ -235,7 +244,6 @@ public class CanonPlaybackControl implements IPlaybackControl try { Log.v(TAG, " showPictureStarted() "); - IPtpIpCommandPublisher publisher = provider.getCommandPublisher(); publisher.flushHoldQueue(); System.gc(); @@ -252,7 +260,6 @@ public class CanonPlaybackControl implements IPlaybackControl try { Log.v(TAG, " showPictureFinished() "); - IPtpIpCommandPublisher publisher = provider.getCommandPublisher(); publisher.flushHoldQueue(); System.gc(); @@ -262,5 +269,4 @@ public class CanonPlaybackControl implements IPlaybackControl e.printStackTrace(); } } - } diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonReducedImageReceiver.kt b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonReducedImageReceiver.kt new file mode 100644 index 0000000..7b848e8 --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonReducedImageReceiver.kt @@ -0,0 +1,217 @@ +package net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.playback + +import android.app.Activity +import android.util.Log +import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback +import net.osdn.gokigen.pkremote.camera.interfaces.playback.IProgressEvent +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpCommandCallback +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.IPtpIpCommandPublisher +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.messages.PtpIpCommandCanonGetPartialObject +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.messages.PtpIpCommandGeneric +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.messages.specific.CanonRequestInnerDevelopEnd +import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.messages.specific.CanonRequestInnerDevelopStart +import java.io.ByteArrayOutputStream + +class CanonReducedImageReceiver(private val activity: Activity, private val publisher: IPtpIpCommandPublisher) : IPtpIpCommandCallback, ICanonSmallImageReceiver +{ + private val mine = this + + private var callback: IDownloadContentCallback? = null + private var objectId = 0 + private var isReceiveMulti = false + private var receivedFirstData = false + + private var receivedTotalBytes = 0 + private var receivedRemainBytes = 0 + + + override fun issueCommand(objectId: Int, callback: IDownloadContentCallback?) + { + Log.v(TAG, " issueCommand() : ${objectId}") + if (this.objectId != 0) + { + // already issued + Log.v(TAG, " COMMAND IS ALREADY ISSUED. : $objectId") + return + } + this.callback = callback + this.objectId = objectId + publisher.enqueueCommand(CanonRequestInnerDevelopStart(object : IPtpIpCommandCallback { + override fun receivedMessage(id: Int, rx_body: ByteArray?) { + Log.v(TAG, " getRequestStatusEvent : $objectId " + (rx_body?.size ?: 0)) + publisher.enqueueCommand(PtpIpCommandGeneric(mine, objectId + 5, false, objectId, 0x9116)) + } + override fun onReceiveProgress(currentBytes: Int, totalBytes: Int, rx_body: ByteArray?) { } + override fun isReceiveMulti(): Boolean { return (false) } + }, objectId, false, objectId, objectId)) // 0x9141 : RequestInnerDevelopStart + } + + override fun receivedMessage(id: Int, rx_body: ByteArray?) + { + try + { + when (id) + { + (objectId + 1) -> { + sendTransferComplete(rx_body) + } + (objectId + 2) -> { + Log.v(TAG, " requestInnerDevelopEnd() : $objectId") + publisher.enqueueCommand(CanonRequestInnerDevelopEnd(this, objectId + 3, false, objectId)) // 0x9143 : RequestInnerDevelopEnd + } + (objectId + 3) -> { + //Log.v(TAG, " --- COMMAND RESET : " + objectId + " --- "); + + // リセットコマンドを送ってみる + publisher.enqueueCommand(PtpIpCommandGeneric(this, objectId + 4, false, objectId, 0x902f)) + } + (objectId + 4) -> { + // 画像取得終了 + Log.v(TAG, " ----- SMALL IMAGE RECEIVE SEQUENCE FINISHED : $objectId") + callback!!.onCompleted() + objectId = 0 + callback = null + receivedTotalBytes = 0 + receivedRemainBytes = 0 + receivedFirstData = false + System.gc() + } + (objectId + 5) -> { + requestGetPartialObject(rx_body) + } + else -> { + Log.v(TAG, " RECEIVED UNKNOWN ID : $id") + } + } + } + catch (e: Exception) + { + e.printStackTrace() + callback?.onErrorOccurred(e) + } + } + + override fun onReceiveProgress(currentBytes: Int, totalBytes: Int, rx_body: ByteArray?) + { + val body = cutHeader(rx_body) + val length = body?.size ?: 0 + Log.v(TAG, " onReceiveProgress() $currentBytes/$totalBytes ($length bytes.)") + callback?.onProgress(body, length, object : IProgressEvent + { + override fun getProgress(): Float { return (currentBytes.toFloat() / totalBytes.toFloat()) } + override fun isCancellable(): Boolean { return (false) } + override fun requestCancellation() { } + }) + } + + private fun cutHeader(rx_body: ByteArray?): ByteArray? + { + if (rx_body == null) + { + return (null) + } + val length = rx_body.size + var dataPosition = 0 + val byteStream = ByteArrayOutputStream() + if (!receivedFirstData) + { + // 初回データを読み込んだ + receivedFirstData = true + + // データを最初に読んだとき。ヘッダ部分を読み飛ばす + dataPosition = rx_body[0].toInt() and 0xff + } + else if (receivedRemainBytes > 0) + { + //Log.v(TAG, " >>> [ remain_bytes : " + received_remain_bytes + "] ( length : " + length + ") " + data_position); + //SimpleLogDumper.dump_bytes("[zzz]", Arrays.copyOfRange(rx_body, data_position, (data_position + 160))); + + // データの読み込みが途中だった場合... + if (length < receivedRemainBytes) + { + // 全部コピーする、足りないバイト数は残す + receivedRemainBytes = receivedRemainBytes - length + receivedTotalBytes = receivedTotalBytes + rx_body.size + return rx_body + } + else + { + byteStream.write(rx_body, dataPosition, receivedRemainBytes) + dataPosition = receivedRemainBytes + receivedRemainBytes = 0 + } + } + while (dataPosition <= length - 12) + { + val bodySize: Int = (rx_body[dataPosition].toUByte().toInt() and 0xff) + (rx_body[dataPosition + 1].toUByte().toInt() and 0xff shl 8) + (rx_body[dataPosition + 2].toUByte().toInt() and 0xff shl 16) + (rx_body[dataPosition + 3].toUByte().toInt() and 0xff shl 24) + if (bodySize <= 12) + { + Log.v(TAG, " BODY SIZE IS SMALL : " + dataPosition + " (" + bodySize + ") [" + receivedRemainBytes + "] " + rx_body.size + " ") + //int startpos = (data_position > 48) ? (data_position - 48) : 0; + //SimpleLogDumper.dump_bytes("[xxx]", Arrays.copyOfRange(rx_body, startpos, (data_position + 48))); + break + } + + // Log.v(TAG, " RX DATA : " + data_position + " (" + body_size + ") [" + received_remain_bytes + "] (" + received_total_bytes + ")"); + //SimpleLogDumper.dump_bytes("[yyy] " + data_position + ": ", Arrays.copyOfRange(rx_body, data_position, (data_position + 64))); + if (dataPosition + bodySize > length) + { + // データがすべてバッファ内になかったときは、バッファすべてコピーして残ったサイズを記憶しておく。 + val copysize = length - (dataPosition + 12) + byteStream.write(rx_body, dataPosition + 12, copysize) + receivedRemainBytes = bodySize - copysize - 12 // マイナス12は、ヘッダ分 + receivedTotalBytes = receivedTotalBytes + copysize + // Log.v(TAG, " --- copy : " + (data_position + 12) + " " + copysize + " remain : " + received_remain_bytes + " body size : " + body_size); + break + } + try + { + byteStream.write(rx_body, dataPosition + 12, bodySize - 12) + dataPosition = dataPosition + bodySize + receivedTotalBytes = receivedTotalBytes + 12 + //Log.v(TAG, " --- COPY : " + (data_position + 12) + " " + (body_size - 12) + " remain : " + received_remain_bytes); + } + catch (e: Exception) + { + Log.v(TAG, " pos : $dataPosition size : $bodySize length : $length") + e.printStackTrace() + } + } + return byteStream.toByteArray() + } + + override fun isReceiveMulti(): Boolean + { + return (isReceiveMulti) + } + + private fun requestGetPartialObject(rx_body: ByteArray?) { + Log.v(TAG, " requestGetPartialObject() : $objectId") + isReceiveMulti = true + receivedFirstData = false + + // 0x9107 : GetPartialObject (元は 0x00020000) + val pictureLength: Int + if (rx_body != null && rx_body.size > 52) { + val dataIndex = 48 + pictureLength = (rx_body[dataIndex].toUByte().toInt() and 0xff) + (rx_body[dataIndex + 1].toUByte().toInt() and 0xff shl 8) + (rx_body[dataIndex + 2].toUByte().toInt() and 0xff shl 16) + (rx_body[dataIndex + 3].toUByte().toInt() and 0xff shl 24) + } + else + { + pictureLength = 0x020000 + } + publisher.enqueueCommand(PtpIpCommandCanonGetPartialObject(this, objectId + 1, false, objectId, 0x01, 0x00, pictureLength, pictureLength)) + } + + private fun sendTransferComplete(rx_body: ByteArray?) + { + Log.v(TAG, " sendTransferComplete(), id : $objectId size: " + (rx_body?.size ?: 0)) + publisher.enqueueCommand(PtpIpCommandGeneric(this, objectId + 2, false, objectId, 0x9117, 4, 0x01)) // 0x9117 : TransferComplete + isReceiveMulti = false + } + + companion object + { + private val TAG = CanonReducedImageReceiver::class.java.simpleName + } +} diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonSmallImageReceiver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonSmallImageReceiver.java index caac0c0..827d7f0 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonSmallImageReceiver.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/CanonSmallImageReceiver.java @@ -18,7 +18,7 @@ import net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.command.messages.sp import java.io.ByteArrayOutputStream; -public class CanonSmallImageReceiver implements IPtpIpCommandCallback +public class CanonSmallImageReceiver implements IPtpIpCommandCallback, ICanonSmallImageReceiver { private static final String TAG = CanonSmallImageReceiver.class.getSimpleName(); @@ -40,6 +40,7 @@ public class CanonSmallImageReceiver implements IPtpIpCommandCallback this.mine = this; } + @Override public void issueCommand(final int objectId, IDownloadContentCallback callback) { if (this.objectId != 0) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/ICanonSmallImageReceiver.java b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/ICanonSmallImageReceiver.java new file mode 100644 index 0000000..1cf837f --- /dev/null +++ b/app/src/main/java/net/osdn/gokigen/pkremote/camera/vendor/ptpip/wrapper/playback/ICanonSmallImageReceiver.java @@ -0,0 +1,8 @@ +package net.osdn.gokigen.pkremote.camera.vendor.ptpip.wrapper.playback; + +import net.osdn.gokigen.pkremote.camera.interfaces.playback.IDownloadContentCallback; + +public interface ICanonSmallImageReceiver +{ + void issueCommand(final int objectId, IDownloadContentCallback callback); +} diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/IPreferencePropertyAccessor.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/IPreferencePropertyAccessor.java index ccaaaf0..751371e 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/IPreferencePropertyAccessor.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/IPreferencePropertyAccessor.java @@ -137,6 +137,9 @@ public interface IPreferencePropertyAccessor String CANON_CONNECTION_SEQUENCE = "canon_connection_mode"; String CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE = "0"; + String CANON_SMALL_PICTURE_TYPE = "canon_small_picture_type"; + String CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE = "0"; + /* //String GR2_DISPLAY_MODE = "gr2_display_mode"; //String GR2_DISPLAY_MODE_DEFAULT_VALUE = "0"; diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/canon/CanonPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/canon/CanonPreferenceFragment.java index a42e2ee..8a3e480 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/canon/CanonPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/canon/CanonPreferenceFragment.java @@ -179,6 +179,9 @@ public class CanonPreferenceFragment extends PreferenceFragmentCompat implement if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/fujix/FujiXPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/fujix/FujiXPreferenceFragment.java index 36028f4..9c5c320 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/fujix/FujiXPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/fujix/FujiXPreferenceFragment.java @@ -179,6 +179,9 @@ public class FujiXPreferenceFragment extends PreferenceFragmentCompat implement if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/nikon/NikonPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/nikon/NikonPreferenceFragment.java index 07967e8..19233a3 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/nikon/NikonPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/nikon/NikonPreferenceFragment.java @@ -176,6 +176,9 @@ public class NikonPreferenceFragment extends PreferenceFragmentCompat implement if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympus/OpcPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympus/OpcPreferenceFragment.java index 97b8e67..4d86cf4 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympus/OpcPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympus/OpcPreferenceFragment.java @@ -205,6 +205,9 @@ public class OpcPreferenceFragment extends PreferenceFragmentCompat implements S if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympuspen/OlympusPenPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympuspen/OlympusPenPreferenceFragment.java index 122b3bc..74ffa82 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympuspen/OlympusPenPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/olympuspen/OlympusPenPreferenceFragment.java @@ -170,6 +170,9 @@ public class OlympusPenPreferenceFragment extends PreferenceFragmentCompat impl if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/panasonic/PanasonicPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/panasonic/PanasonicPreferenceFragment.java index 4dc8f98..aac176c 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/panasonic/PanasonicPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/panasonic/PanasonicPreferenceFragment.java @@ -168,6 +168,9 @@ public class PanasonicPreferenceFragment extends PreferenceFragmentCompat imple if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/pixpro/PixproPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/pixpro/PixproPreferenceFragment.java index 63d80bf..f6e872c 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/pixpro/PixproPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/pixpro/PixproPreferenceFragment.java @@ -167,6 +167,9 @@ public class PixproPreferenceFragment extends PreferenceFragmentCompat implemen if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/ricohgr2/RicohGr2PreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/ricohgr2/RicohGr2PreferenceFragment.java index d0d9392..623a6af 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/ricohgr2/RicohGr2PreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/ricohgr2/RicohGr2PreferenceFragment.java @@ -183,6 +183,9 @@ public class RicohGr2PreferenceFragment extends PreferenceFragmentCompat implem if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/sony/SonyPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/sony/SonyPreferenceFragment.java index 53fdcea..278dc5e 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/sony/SonyPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/sony/SonyPreferenceFragment.java @@ -165,6 +165,9 @@ public class SonyPreferenceFragment extends PreferenceFragmentCompat implements if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/java/net/osdn/gokigen/pkremote/preference/theta/ThetaPreferenceFragment.java b/app/src/main/java/net/osdn/gokigen/pkremote/preference/theta/ThetaPreferenceFragment.java index 777f1f4..4c2b943 100644 --- a/app/src/main/java/net/osdn/gokigen/pkremote/preference/theta/ThetaPreferenceFragment.java +++ b/app/src/main/java/net/osdn/gokigen/pkremote/preference/theta/ThetaPreferenceFragment.java @@ -167,6 +167,9 @@ public class ThetaPreferenceFragment extends PreferenceFragmentCompat implement if (!items.containsKey(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE)) { editor.putString(IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE, IPreferencePropertyAccessor.CANON_CONNECTION_SEQUENCE_DEFAULT_VALUE); } + if (!items.containsKey(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE)) { + editor.putString(IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE, IPreferencePropertyAccessor.CANON_SMALL_PICTURE_TYPE_DEFAULT_VALUE); + } editor.apply(); } catch (Exception e) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index fce3479..4c607b8 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -378,4 +378,7 @@ 接続シーケンス 通常、変更は不要です (初期値: TYPE0) + スモール画像取得シーケンス + (初期値: TYPE0) + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index e08ec12..d639701 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -116,4 +116,14 @@ 2 + + TYPE0 + TYPE1 + + + + 0 + 1 + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1276303..0dd75af 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -380,4 +380,7 @@ Connection Sequence default: TYPE0 + Small Picture Getting Sequence + default: TYPE0 + diff --git a/app/src/main/res/xml/preferences_canon.xml b/app/src/main/res/xml/preferences_canon.xml index 7a2f31c..b14e7bb 100644 --- a/app/src/main/res/xml/preferences_canon.xml +++ b/app/src/main/res/xml/preferences_canon.xml @@ -50,6 +50,14 @@ android:summary="@string/pref_summary_canon_host_ip" /> + +