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
|
||||
# List C/C++ source files with relative paths to this CMakeLists.txt.
|
||||
ktaglib.cpp
|
||||
AndroidIOStream.cpp)
|
||||
JVMInputStream.cpp)
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
# 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.
|
||||
//
|
||||
|
||||
#ifndef AUXIO_ANDROIDIOSTREAM_H
|
||||
#define AUXIO_ANDROIDIOSTREAM_H
|
||||
#ifndef AUXIO_JVMINPUTSTREAM_H
|
||||
#define AUXIO_JVMINPUTSTREAM_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include "taglib/tiostream.h"
|
||||
|
||||
class AndroidIOStream : public TagLib::IOStream {
|
||||
class JVMInputStream : public TagLib::IOStream {
|
||||
public:
|
||||
AndroidIOStream(JNIEnv *env, jobject &fileRef);
|
||||
JVMInputStream(JNIEnv *env, jobject inputStream);
|
||||
|
||||
~AndroidIOStream();
|
||||
~JVMInputStream();
|
||||
|
||||
AndroidIOStream(const AndroidIOStream &) = delete;
|
||||
AndroidIOStream &operator=(const AndroidIOStream &) = delete;
|
||||
JVMInputStream(const JVMInputStream &) = delete;
|
||||
JVMInputStream &operator=(const JVMInputStream &) = delete;
|
||||
|
||||
/*!
|
||||
* Returns the stream name in the local file system encoding.
|
||||
|
@ -99,8 +99,18 @@ public:
|
|||
|
||||
private:
|
||||
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 <string>
|
||||
|
||||
#include "AndroidIOStream.h"
|
||||
#include "JVMInputStream.h"
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/tag.h>
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_org_oxycblt_ktaglib_KTagLib_load(
|
||||
Java_org_oxycblt_ktaglib_KTagLib_openNative(
|
||||
JNIEnv* env,
|
||||
jobject /* this */,
|
||||
jobject fileRef) {
|
||||
AndroidIOStream stream { env, fileRef };
|
||||
jobject inputStream) {
|
||||
JVMInputStream stream { env, inputStream };
|
||||
TagLib::FileRef file { &stream };
|
||||
if (file.isNull()) {
|
||||
return nullptr;
|
||||
|
@ -27,7 +27,8 @@ Java_org_oxycblt_ktaglib_KTagLib_load(
|
|||
|
||||
jclass tagClass = env->FindClass("org/oxycblt/ktaglib/Tag");
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
import java.io.InputStream
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
object KTagLib {
|
||||
// Used to load the 'ktaglib' library on application startup.
|
||||
init {
|
||||
System.loadLibrary("ktaglib")
|
||||
}
|
||||
|
||||
/**
|
||||
* A native method that is implemented by the 'ktaglib' native library,
|
||||
* which is packaged with this application.
|
||||
* Open a file and extract a tag.
|
||||
*
|
||||
* 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(
|
||||
val fileName: String,
|
||||
val inputStream: InputStream
|
||||
val uri: Uri
|
||||
)
|
||||
|
||||
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