ktaglib: implement iostream/file shim
This commit is contained in:
parent
55e77707ea
commit
be54ee9c18
8 changed files with 224 additions and 86 deletions
|
@ -1,65 +0,0 @@
|
||||||
//
|
|
||||||
// Created by oxycblt on 12/12/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "AndroidIOStream.h"
|
|
||||||
|
|
||||||
|
|
||||||
AndroidIOStream::AndroidIOStream(JNIEnv *env, jobject &fileRef) : env(env), fileRef(fileRef) {}
|
|
||||||
|
|
||||||
AndroidIOStream::~AndroidIOStream() {}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Returns the stream name in the local file system encoding.
|
|
||||||
*/
|
|
||||||
TagLib::FileName AndroidIOStream::name() const {
|
|
||||||
return TagLib::FileName();
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Reads a block of size \a length at the current get pointer.
|
|
||||||
*/
|
|
||||||
TagLib::ByteVector AndroidIOStream::readBlock(size_t length) {
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::writeBlock(const TagLib::ByteVector &data) {
|
|
||||||
throw std::runtime_error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::insert(const TagLib::ByteVector &data,
|
|
||||||
TagLib::offset_t start, size_t replace) {
|
|
||||||
throw std::runtime_error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::removeBlock(TagLib::offset_t start, size_t length) {
|
|
||||||
throw std::runtime_error("Not implemented");
|
|
||||||
};
|
|
||||||
|
|
||||||
bool AndroidIOStream::readOnly() const {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool AndroidIOStream::isOpen() const {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::seek(TagLib::offset_t offset, Position p) {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::clear() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TagLib::offset_t AndroidIOStream::tell() const {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
TagLib::offset_t AndroidIOStream::length() {
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
void AndroidIOStream::truncate(TagLib::offset_t length) {
|
|
||||||
|
|
||||||
};
|
|
|
@ -46,7 +46,7 @@ set_target_properties(
|
||||||
add_library(${CMAKE_PROJECT_NAME} SHARED
|
add_library(${CMAKE_PROJECT_NAME} SHARED
|
||||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||||
ktaglib.cpp
|
ktaglib.cpp
|
||||||
AndroidIOStream.cpp)
|
JVMInputStream.cpp)
|
||||||
|
|
||||||
# Specifies libraries CMake should link to your target library. You
|
# Specifies libraries CMake should link to your target library. You
|
||||||
# can link libraries from various origins, such as libraries defined in this
|
# can link libraries from various origins, such as libraries defined in this
|
||||||
|
|
101
ktaglib/src/main/cpp/JVMInputStream.cpp
Normal file
101
ktaglib/src/main/cpp/JVMInputStream.cpp
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
//
|
||||||
|
// Created by oxycblt on 12/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "JVMInputStream.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
// TODO: Handle stream exceptions
|
||||||
|
|
||||||
|
JVMInputStream::JVMInputStream(JNIEnv *env, jobject inputStream) :
|
||||||
|
env(env), inputStream(inputStream) {
|
||||||
|
if (!env->IsInstanceOf(inputStream, env->FindClass("org/oxycblt/ktaglib/NativeInputStream"))) {
|
||||||
|
throw std::runtime_error("oStream is not an instance of TagLibOStream");
|
||||||
|
}
|
||||||
|
jclass inputStreamClass = env->FindClass("org/oxycblt/ktaglib/NativeInputStream");
|
||||||
|
inputStreamNameMethod = env->GetMethodID(inputStreamClass, "name", "()Ljava/lang/String;");
|
||||||
|
inputStreamReadBlockMethod = env->GetMethodID(inputStreamClass, "readBlock", "(J)[B");
|
||||||
|
inputStreamIsOpenMethod = env->GetMethodID(inputStreamClass, "isOpen", "()Z");
|
||||||
|
inputStreamSeekFromBeginningMethod = env->GetMethodID(inputStreamClass, "seekFromBeginning",
|
||||||
|
"(J)V");
|
||||||
|
inputStreamSeekFromCurrentMethod = env->GetMethodID(inputStreamClass, "seekFromCurrent",
|
||||||
|
"(J)V");
|
||||||
|
inputStreamSeekFromEndMethod = env->GetMethodID(inputStreamClass, "seekFromEnd", "(J)V");
|
||||||
|
inputStreamClearMethod = env->GetMethodID(inputStreamClass, "clear", "()V");
|
||||||
|
inputStreamTellMethod = env->GetMethodID(inputStreamClass, "tell", "()J");
|
||||||
|
inputStreamLengthMethod = env->GetMethodID(inputStreamClass, "length", "()J");
|
||||||
|
}
|
||||||
|
|
||||||
|
JVMInputStream::~JVMInputStream() {}
|
||||||
|
|
||||||
|
TagLib::FileName JVMInputStream::name() const {
|
||||||
|
auto name = (jstring) env->CallObjectMethod(inputStream, inputStreamNameMethod);
|
||||||
|
const char *nameChars = env->GetStringUTFChars(name, nullptr);
|
||||||
|
auto fileName = TagLib::FileName(nameChars);
|
||||||
|
env->ReleaseStringUTFChars(name, nameChars);
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
TagLib::ByteVector JVMInputStream::readBlock(size_t length) {
|
||||||
|
auto data = (jbyteArray) env->CallObjectMethod(inputStream, inputStreamReadBlockMethod, length);
|
||||||
|
jsize dataLength = env->GetArrayLength(data);
|
||||||
|
auto dataBytes = env->GetByteArrayElements(data, nullptr);
|
||||||
|
TagLib::ByteVector byteVector(reinterpret_cast<const char *>(dataBytes), dataLength);
|
||||||
|
env->ReleaseByteArrayElements(data, dataBytes, JNI_ABORT);
|
||||||
|
return byteVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::writeBlock(const TagLib::ByteVector &data) {
|
||||||
|
throw std::runtime_error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::insert(const TagLib::ByteVector &data,
|
||||||
|
TagLib::offset_t start, size_t replace) {
|
||||||
|
throw std::runtime_error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::removeBlock(TagLib::offset_t start, size_t length) {
|
||||||
|
throw std::runtime_error("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JVMInputStream::readOnly() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JVMInputStream::isOpen() const {
|
||||||
|
return env->CallBooleanMethod(inputStream, inputStreamIsOpenMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::seek(TagLib::offset_t offset, Position p) {
|
||||||
|
auto joffset = static_cast<jlong>(std::llround(offset));
|
||||||
|
switch (p) {
|
||||||
|
case Beginning:
|
||||||
|
env->CallVoidMethod(inputStream, inputStreamSeekFromBeginningMethod, joffset);
|
||||||
|
break;
|
||||||
|
case Current:
|
||||||
|
env->CallVoidMethod(inputStream, inputStreamSeekFromCurrentMethod, joffset);
|
||||||
|
break;
|
||||||
|
case End:
|
||||||
|
env->CallVoidMethod(inputStream, inputStreamSeekFromEndMethod, joffset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::clear() {
|
||||||
|
env->CallVoidMethod(inputStream, inputStreamClearMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
TagLib::offset_t JVMInputStream::tell() const {
|
||||||
|
jlong jposition = env->CallLongMethod(inputStream, inputStreamTellMethod);
|
||||||
|
return static_cast<TagLib::offset_t>(jposition);
|
||||||
|
}
|
||||||
|
|
||||||
|
TagLib::offset_t JVMInputStream::length() {
|
||||||
|
jlong jlength = env->CallLongMethod(inputStream, inputStreamLengthMethod);
|
||||||
|
return static_cast<TagLib::offset_t>(jlength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JVMInputStream::truncate(TagLib::offset_t length) {
|
||||||
|
throw std::runtime_error("Not implemented");
|
||||||
|
}
|
|
@ -2,21 +2,21 @@
|
||||||
// Created by oxycblt on 12/12/24.
|
// Created by oxycblt on 12/12/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef AUXIO_ANDROIDIOSTREAM_H
|
#ifndef AUXIO_JVMINPUTSTREAM_H
|
||||||
#define AUXIO_ANDROIDIOSTREAM_H
|
#define AUXIO_JVMINPUTSTREAM_H
|
||||||
|
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
|
||||||
#include "taglib/tiostream.h"
|
#include "taglib/tiostream.h"
|
||||||
|
|
||||||
class AndroidIOStream : public TagLib::IOStream {
|
class JVMInputStream : public TagLib::IOStream {
|
||||||
public:
|
public:
|
||||||
AndroidIOStream(JNIEnv *env, jobject &fileRef);
|
JVMInputStream(JNIEnv *env, jobject inputStream);
|
||||||
|
|
||||||
~AndroidIOStream();
|
~JVMInputStream();
|
||||||
|
|
||||||
AndroidIOStream(const AndroidIOStream &) = delete;
|
JVMInputStream(const JVMInputStream &) = delete;
|
||||||
AndroidIOStream &operator=(const AndroidIOStream &) = delete;
|
JVMInputStream &operator=(const JVMInputStream &) = delete;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Returns the stream name in the local file system encoding.
|
* Returns the stream name in the local file system encoding.
|
||||||
|
@ -99,8 +99,18 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
jobject &fileRef;
|
jobject inputStream;
|
||||||
|
jmethodID inputStreamNameMethod;
|
||||||
|
jmethodID inputStreamReadBlockMethod;
|
||||||
|
jmethodID inputStreamIsOpenMethod;
|
||||||
|
jmethodID inputStreamSeekFromBeginningMethod;
|
||||||
|
jmethodID inputStreamSeekFromCurrentMethod;
|
||||||
|
jmethodID inputStreamSeekFromEndMethod;
|
||||||
|
jmethodID inputStreamClearMethod;
|
||||||
|
jmethodID inputStreamTellMethod;
|
||||||
|
jmethodID inputStreamLengthMethod;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif //AUXIO_ANDROIDIOSTREAM_H
|
#endif //AUXIO_JVMINPUTSTREAM_H
|
|
@ -1,16 +1,16 @@
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "AndroidIOStream.h"
|
#include "JVMInputStream.h"
|
||||||
#include <taglib/fileref.h>
|
#include <taglib/fileref.h>
|
||||||
#include <taglib/tag.h>
|
#include <taglib/tag.h>
|
||||||
|
|
||||||
extern "C" JNIEXPORT jobject JNICALL
|
extern "C" JNIEXPORT jobject JNICALL
|
||||||
Java_org_oxycblt_ktaglib_KTagLib_load(
|
Java_org_oxycblt_ktaglib_KTagLib_openNative(
|
||||||
JNIEnv* env,
|
JNIEnv* env,
|
||||||
jobject /* this */,
|
jobject /* this */,
|
||||||
jobject fileRef) {
|
jobject inputStream) {
|
||||||
AndroidIOStream stream { env, fileRef };
|
JVMInputStream stream { env, inputStream };
|
||||||
TagLib::FileRef file { &stream };
|
TagLib::FileRef file { &stream };
|
||||||
if (file.isNull()) {
|
if (file.isNull()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -27,7 +27,8 @@ Java_org_oxycblt_ktaglib_KTagLib_load(
|
||||||
|
|
||||||
jclass tagClass = env->FindClass("org/oxycblt/ktaglib/Tag");
|
jclass tagClass = env->FindClass("org/oxycblt/ktaglib/Tag");
|
||||||
jmethodID tagInit = env->GetMethodID(tagClass, "<init>", "(Ljava/util/Map;Ljava/util/Map;[B)V");
|
jmethodID tagInit = env->GetMethodID(tagClass, "<init>", "(Ljava/util/Map;Ljava/util/Map;[B)V");
|
||||||
// Create tag
|
|
||||||
jobject tagObj = env->NewObject(tagClass, tagInit, id3v2, vorbis, coverData);
|
jobject tagObj = env->NewObject(tagClass, tagInit, id3v2, vorbis, coverData);
|
||||||
|
|
||||||
return tagObj;
|
return tagObj;
|
||||||
}
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package org.oxycblt.ktaglib
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class AndroidInputStream(
|
||||||
|
context: Context,
|
||||||
|
fileRef: FileRef
|
||||||
|
) : NativeInputStream {
|
||||||
|
private val fileName = fileRef.fileName
|
||||||
|
private val fd = requireNotNull(context.contentResolver.openFileDescriptor(fileRef.uri, "r")) {
|
||||||
|
"Failed to open file descriptor for ${fileRef.fileName}"
|
||||||
|
}
|
||||||
|
private val fis = FileInputStream(fd.fileDescriptor)
|
||||||
|
private val channel = fis.channel
|
||||||
|
|
||||||
|
override fun name() = fileName
|
||||||
|
|
||||||
|
override fun readBlock(length: Long): ByteArray {
|
||||||
|
val buffer = ByteBuffer.allocate(length.toInt())
|
||||||
|
channel.read(buffer)
|
||||||
|
return buffer.array()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isOpen(): Boolean {
|
||||||
|
return channel.isOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seekFromBeginning(offset: Long) {
|
||||||
|
channel.position(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seekFromCurrent(offset: Long) {
|
||||||
|
channel.position(channel.position() + offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun seekFromEnd(offset: Long) {
|
||||||
|
channel.position(channel.size() - offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
// Nothing to clear
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tell() = channel.position()
|
||||||
|
|
||||||
|
override fun length() = channel.size()
|
||||||
|
|
||||||
|
fun close() {
|
||||||
|
channel.close()
|
||||||
|
fis.close()
|
||||||
|
fd.close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,32 @@
|
||||||
package org.oxycblt.ktaglib
|
package org.oxycblt.ktaglib
|
||||||
|
|
||||||
import java.io.InputStream
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
object KTagLib {
|
object KTagLib {
|
||||||
// Used to load the 'ktaglib' library on application startup.
|
|
||||||
init {
|
init {
|
||||||
System.loadLibrary("ktaglib")
|
System.loadLibrary("ktaglib")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A native method that is implemented by the 'ktaglib' native library,
|
* Open a file and extract a tag.
|
||||||
* which is packaged with this application.
|
*
|
||||||
|
* Note: This method is blocking and should be handled as such if
|
||||||
|
* calling from a coroutine.
|
||||||
*/
|
*/
|
||||||
external fun load(fileRef: FileRef): Tag?
|
fun open(context: Context, ref: FileRef): Tag? {
|
||||||
|
val inputStream = AndroidInputStream(context, ref)
|
||||||
|
val tag = openNative(inputStream)
|
||||||
|
inputStream.close()
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun openNative(ioStream: AndroidInputStream): Tag?
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FileRef(
|
data class FileRef(
|
||||||
val fileName: String,
|
val fileName: String,
|
||||||
val inputStream: InputStream
|
val uri: Uri
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Tag(
|
data class Tag(
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.oxycblt.ktaglib
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java interface for the read-only methods in TagLib's IOStream API.
|
||||||
|
*
|
||||||
|
* The vast majority of IO shim between Taglib/KTaglib should occur here
|
||||||
|
* to minimize JNI calls.
|
||||||
|
*/
|
||||||
|
interface NativeInputStream {
|
||||||
|
fun name(): String
|
||||||
|
|
||||||
|
fun readBlock(length: Long): ByteArray
|
||||||
|
|
||||||
|
fun isOpen(): Boolean
|
||||||
|
|
||||||
|
fun seekFromBeginning(offset: Long)
|
||||||
|
|
||||||
|
fun seekFromCurrent(offset: Long)
|
||||||
|
|
||||||
|
fun seekFromEnd(offset: Long)
|
||||||
|
|
||||||
|
fun clear()
|
||||||
|
|
||||||
|
fun tell(): Long
|
||||||
|
|
||||||
|
fun length(): Long
|
||||||
|
}
|
Loading…
Reference in a new issue