Error setting extractor data source, err -10002 when using AMediaExtractor with audio file located in media store - android-ndk

I am trying to fetch an audio file from MediaStore, extract audio data from it, decode it, and play it, on Android 10.
I am getting the following error when I call setDataSource on my MediaExtractor instance:
Error setting extractor data source, err -10002
To reproduce:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val path = getSong().get(0).path
stringToJNI("file://"+ path)
val URI = Uri.parse(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString() + "/" + getSong().get(0).id)
stringToJNI(URI!!.toString())
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
external fun stringToJNI(URI: String)
companion object {
// Used to load the 'native-lib' library on application startup.
init {
System.loadLibrary("native-lib")
}
}
fun getSong() : MutableList<Songs> {
val SONGS_PROJECTION = arrayOf(
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.DATA
)
val cursor = contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
SONGS_PROJECTION,
null,
null,
MediaStore.Audio.Media._ID +
" ASC LIMIT 100"
)
val items: MutableList<Songs> = mutableListOf()
cursor?.let {
if (cursor.count > 0) {
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val s0 = cursor.getString(cursor.getColumnIndex(SONGS_PROJECTION[0]))
val s1 = cursor.getString(cursor.getColumnIndex(SONGS_PROJECTION[1]))
val s2 = cursor.getString(cursor.getColumnIndex(SONGS_PROJECTION[2]))
items.add(Songs(s0, s1, s2))
cursor.moveToNext()
}
}
cursor.close()
}
return items
}
}
In MainActivity, I pass to native side once the path and second time the URI, each time without luck.
#include <jni.h>
#include <string>
#include <media/NdkMediaExtractor.h>
#include <android/log.h>
#include <bitset>
#define APP_NAME "MediaStoreToNativeAudio"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, APP_NAME, __VA_ARGS__))
extern "C" JNIEXPORT void JNICALL
Java_com_example_mediastoretonativeaudio_MainActivity_stringToJNI(
JNIEnv *env,
jobject jobj,
jstring URI) {
const char *uri = env->GetStringUTFChars(URI, NULL);
std::string s(uri);
AMediaExtractor *extractor = AMediaExtractor_new();
media_status_t amresult = AMediaExtractor_setDataSource(extractor, uri);
if (amresult != AMEDIA_OK) {
LOGE("AMediaExtractor_setDataSource called with: [%s]", s.c_str());
LOGE("Error setting extractor data source, err %d", amresult);
}
return;
}
from logs:
2019-11-20 01:09:03.519 8270-8270/com.example.mediastoretonativeaudio E/NdkMediaExtractor: can't create http service
2019-11-20 01:09:03.519 8270-8270/com.example.mediastoretonativeaudio E/MediaStoreToNativeAudio: AMediaExtractor_setDataSource called with: [file:///storage/emulated/0/Music/Thank you for the drum machine/01 - Everything Moves.mp3]
2019-11-20 01:09:03.519 8270-8270/com.example.mediastoretonativeaudio E/MediaStoreToNativeAudio: Error setting extractor data source, err -10002
2019-11-20 01:09:03.543 8270-8270/com.example.mediastoretonativeaudio E/NdkMediaExtractor: can't create http service
2019-11-20 01:09:03.543 8270-8270/com.example.mediastoretonativeaudio E/MediaStoreToNativeAudio: AMediaExtractor_setDataSource called with: [content://media/external/audio/media/472]
2019-11-20 01:09:03.543 8270-8270/com.example.mediastoretonativeaudio E/MediaStoreToNativeAudio: Error setting extractor data source, err -10002
My manifest:
After installing app I also give permission through settings.
Edit:
A public repository with test code: https://github.com/AndrewBloom/MediaStoreToNativeAudioSample
the same behaviour results using:
android:requestLegacyExternalStorage="true"

This to me seems a bug on Android 10. It seems that android:requestLegacyExternalStorage="true" does not change the situation. You may have to request on manifest and ask same permission at runtime. The AMediaExtractor_setDataSource function must be called on a thread that is attached to Java. Doing all of that correctly will allow you to make it work on other versions of Android but not on Android 10. I've reported the issue on Android Bug Tracker here: https://issuetracker.google.com/144837266 As per google answer, it seems that all the app using native libraries that require file access through path can be affected and they know the issue https://www.youtube.com/watch?v=UnJ3amzJM94 .
A workaround in my case was to use AMediaExtractor_setDataSourceFd, getting the file descriptor at Java level through contentResolver and its method openFileDescriptor.

Related

Android app fails bluetooth connection with ESP when calling connect()

I'm trying to build an Android app in Android Studio using Kotlin to send some simple data between an ESP32 and a mobile over Bluetooth. I've been following along a number of tutorials but just can't seem to get the connection established, permissions and scanning for devices looks to be working correctly. When I call connect() on the socket the app hangs for a few seconds and then crashes with this error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.btleveller, PID: 28899
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=0, result=-1, data=Intent { (has extras) }} to activity {com.example.btleveller/com.example.btleveller.MainActivity}: java.io.IOException: read failed, socket might closed or timeout, read ret: -1
at android.app.ActivityThread.deliverResults(ActivityThread.java:5368)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:5407)
etc... I can post the full output if it's helpful
My ESP is running some very basic helloworld style code using the NimBLE-Arduino code, programmed through VSCode with the PlatformIO extension. I think this side of it is all working correct as I can see the device in the "nRF Connect" app on my mobile. The scanning is done through the CompanionDeviceManager library:
I thought maybe there was a problem with the UUID I was supplying, or that I needed to make changes for BLE as opposed to regular Bluetooth but so far nothing I've found online has worked. I've also tried using "createL2capChannel()" to create the socket but got stuck on the PSM value. These are the relevant bits of code:
//private val ESP_UUID = UUID.fromString("0000dead-0000-1000-8000-00805F9B34FB")
private val ESP_UUID = UUID.fromString("0000baad-0000-1000-8000-00805F9B34FB")
...
// Look for connection, kicked off by button press
fun lookForConn(view: View) {
val deviceFilter: BluetoothDeviceFilter = BluetoothDeviceFilter.Builder()
.setNamePattern(Pattern.compile("BLE"))
.build()
// The argument provided in setSingleDevice() determines whether a single
// device name or a list of them appears.
val pairingRequest: AssociationRequest = AssociationRequest.Builder()
.addDeviceFilter(deviceFilter)
.setSingleDevice(false)
.build()
// When the app tries to pair with a Bluetooth device, show the
// corresponding dialog box to the user.
deviceManager.associate(pairingRequest,
object : CompanionDeviceManager.Callback() {
override fun onDeviceFound(chooserLauncher: IntentSender) {
startIntentSenderForResult(chooserLauncher,
SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0)
}
override fun onFailure(error: CharSequence?) {
// Handle the failure.
Log.d("DEVHandler","failed to find dev?")
}
}, null)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
SELECT_DEVICE_REQUEST_CODE -> when(resultCode) {
Activity.RESULT_OK -> {
// The user chose to pair the app with a Bluetooth device.
val deviceToPair: BluetoothDevice? =
data?.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
Log.d("DEVHandler","try to bond:" + deviceToPair?.name)
deviceToPair?.let { device ->
device.createBond()
val newConn = ConnectThread(deviceToPair).run()
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
private inner class ConnectThread(device: BluetoothDevice) : Thread() {
private var mHaveConn = false
public fun IsConnected(): Boolean {
return mHaveConn
}
private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
//device.createRfcommSocketToServiceRecord(ESP_UUID)
device.createInsecureRfcommSocketToServiceRecord(ESP_UUID)
}
public override fun run() {
// Cancel discovery because it otherwise slows down the connection.
BTMan.mBTAdapter?.cancelDiscovery()
mmSocket?.let { socket ->
// Connect to the remote device through the socket. This call blocks
// until it succeeds or throws an exception.
if (socket == null)
Log.d("CONNThread", "Socket is null...")
if (socket.isConnected == true)
Log.d("CONNThread", "Socket is already connected...")
socket.connect()
Log.d("CONNThread", "Made a connection")
// The connection attempt succeeded. Perform work associated with
// the connection in a separate thread.
//manageMyConnectedSocket(socket)
mHaveConn = true
}
}
// Closes the client socket and causes the thread to finish.
fun cancel() {
try {
mmSocket?.close()
} catch (e: IOException) {
Log.e("CONNThread", "Could not close the client socket", e)
}
mHaveConn = false
}
}

AndroidStudio and Kotlin: What is Error Expecting Member Declaration

I'm learning myself some android programming (beginning level) and following a tutorial. I'm getting an error when I run/build the project. Expecting member declaration. I've checked the code for typos and syntax errors. I've googled this, but I'm just not sure what it means or what to look for to fix it.
Are other classes used in the project automatically seen by the MainActivity class?
Code in part:
MainActivity.kt:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
val friendlyDestroyer = Destroyer("Invincible")
val friendlyCarrier = Carrier("Indomitable")
val enemyDestroyer = Destroyer("Grey Death")
val enemyCarrier = Carrier("Big Grey Death")
val friendlyShipyard = ShipYard()
friendlyDestroyer.takeDamage(enemyDestroyer.shootShell())
friendlyDestroyer.takeDamage(enemyCarrier.launchAerialAttack())
// Fight back
enemyCarrier.takeDamage(friendlyCarrier.launchAerialAttack())
enemyCarrier.takeDamage(friendlyDestroyer.shootShell())
...
Any line that has an instance to a class with a function call shows the red squiggly line and the error.
In the line: friendlyDestroyer.takeDamage(enemyDestroyer.shootShell()), it shows the expecting member declaration error at just about every part of the line.
This happens on every instance of a class making a call to a class.
I'm not seeing any errors for the other classes/files.
Destroyer.kt:
package com.johndcowan.basicclasses
class Destroyer(name: String) {
// what is the name of the ship
var name: String = ""
private set
// what type of ship is it
// alwys a destroyer
val type = "Destroyer"
// how much the ship can take before sinking
private var hullIntegrity = 200
// how many shots left in the arsenal
var ammo = 1
// cannot be directly set externally
private set
// no external access whatsoever
private var shotPower = 60
// has the ship been sunk
private var sunk = false
// this code runs as the instance is being initialized
init {
// so we can use the name parameter
this.name = "$type $name"
}
fun takeDamage(damageTaken: Int) {
if (!sunk) {
hullIntegrity -= damageTaken
println("$name hull integrity = $hullIntegrity")
if (hullIntegrity <= 0){
println("Destroyer $name has been sunk")
sunk = true
}
} else {
// Already sunk
println("Error Ship does not exist")
}
}
fun shootShell():Int {
// let the calling code know how much damage to do
return if (ammo > 0) {
ammo--
shotPower
}else{
0
}
}
...
What am I missing or not seeing?
Thanks for any tips.

Why am I getting a local reference table overflow?

I'm trying to figure out how to resolve a crash happening on my code when the number of cue points is high. My test reproduce it every time with a number of 530 cue items. Each cue has a dataSize of 1.
You can see my code below. Essentially native_update_cue_points is called from the java layer. This triggers multiple calls to the java layer from jni layer. Somehow this is causing a table overflow. I'm thinking somehow each call to the static java function is not releasing the allocated string values causing the stack overflow?
Sample code
static void native_update_cue_points(JNIEnv *env, jclass clazz)
{
if (smpMediaPlayer)
{
std::list<Cue> cuePoints;
smpMediaPlayer->getCuePoints(&cuePoints);
for (std::list<Cue>::iterator it = cuePoints.begin(); it != cuePoints.end(); it++)
{
Cue cue = *it;
java_update_cue_point(env, clazz, smFields.global_ref_thiz, 0, &cue);
}
}
}
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
if (!pCue) {
return;
}
jlong id = pCue->id;
jint type = pCue->type;
jint extra = pCue->extra;
jlong pos = pCue->pos;
jlong duration = pCue->duration;
jobjectArray jkeys = NULL;
jobjectArray jvalues = NULL;
int dataSize = pCue->data.size();
if (0 < dataSize)
{
jkeys = env->NewObjectArray(dataSize, env->FindClass("java/lang/String"), NULL);
jvalues = env->NewObjectArray(dataSize, env->FindClass("[B"), NULL);
int position = 0;
for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
{
// set key
env->SetObjectArrayElement(jkeys, position, env->NewStringUTF(it->first.c_str()));
// set value
jbyteArray byteArray = env->NewByteArray(it->second.length());
env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
env->SetObjectArrayElement(jvalues, position, byteArray);
env->DeleteLocalRef(byteArray);
position++;
}
}
// report update
env->CallStaticVoidMethod(
clazz,
smFields.native_callback_add_cue_point,
thiz,
(jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);
}
Stacktrace
JNI ERROR (app bug): local reference table overflow (max=512)
local reference table dump:
Last 10 entries (of 512):
511: 0x13054090 java.lang.String[] (1 elements)
510: 0x70732980 java.lang.Class<java.lang.String>
509: 0x130517c0 java.lang.String "song_artist"
508: 0x13054080 byte[][] (1 elements)
507: 0x7079af18 java.lang.Class<byte[]>
506: 0x13054070 java.lang.String[] (1 elements)
505: 0x70732980 java.lang.Class<java.lang.String>
504: 0x13051700 java.lang.String "song_artist"
503: 0x12d17ff0 byte[][] (1 elements)
502: 0x7079af18 java.lang.Class<byte[]>
Summary:
205 of java.lang.Class (2 unique instances)
103 of java.lang.String[] (1 elements) (103 unique instances)
102 of java.lang.String (102 unique instances)
102 of byte[][] (1 elements) (102 unique instances)
UPDATE 1:
I did a bit more digging and was able to narrow it down to the call FindClass. This leaves me even more confused! :/ Here is my simplified native_update_cue_point method. Do I need to release somehow the FindClass call!?
Code
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
jclass jstringClass = env->FindClass("java/lang/String");
}
Stacktrace
JNI ERROR (app bug): local reference table overflow (max=512)
local reference table dump:
Last 10 entries (of 512):
511: 0x70732980 java.lang.Class<java.lang.String>
510: 0x70732980 java.lang.Class<java.lang.String>
509: 0x70732980 java.lang.Class<java.lang.String>
508: 0x70732980 java.lang.Class<java.lang.String>
507: 0x70732980 java.lang.Class<java.lang.String>
506: 0x70732980 java.lang.Class<java.lang.String>
505: 0x70732980 java.lang.Class<java.lang.String>
504: 0x70732980 java.lang.Class<java.lang.String>
503: 0x70732980 java.lang.Class<java.lang.String>
502: 0x70732980 java.lang.Class<java.lang.String>
Summary:
512 of java.lang.Class (1 unique instances)
UPDATE 2:
I was able to fix the table overflow by doing a DeleteLocalRef on every local object regardless if the java VM would take care of it. My thoughts are that the VM is accumulating items on the stack which will release after executing. I need to remove each item explicitly from the stack to avoid unwanted accumulations for later removal while on a large loop.
Still I'm left feeling like I haven't understood completely why this is happening and why my solution solves it.
Fixed code
static void java_update_cue_point (JNIEnv *env, jclass clazz, jobject thiz, int reqType, Cue *pCue)
{
if (!pCue) {
return;
}
jclass jstringClass = env->FindClass("java/lang/String");
jclass jbyteArrayClass = env->FindClass("[B");
jlong id = pCue->id;
jint type = pCue->type;
jint extra = pCue->extra;
jlong pos = pCue->pos;
jlong duration = pCue->duration;
jobjectArray jkeys = NULL;
jobjectArray jvalues = NULL;
int dataSize = pCue->data.size();
if (0 < dataSize)
{
jkeys = env->NewObjectArray(dataSize, jstringClass, NULL);
jvalues = env->NewObjectArray(dataSize, jbyteArrayClass, NULL);
int position = 0;
for (std::map<std::string, std::string>::iterator it = pCue->data.begin(); it != pCue->data.end(); it++)
{
// set key
jstring jkey = env->NewStringUTF(it->first.c_str());
env->SetObjectArrayElement(jkeys, position, jkey);
env->DeleteLocalRef(jkey); // FIX
// set value
jbyteArray byteArray = env->NewByteArray(it->second.length());
env->SetByteArrayRegion(byteArray, 0, it->second.length(), (const signed char *)it->second.c_str());
env->SetObjectArrayElement(jvalues, position, byteArray);
env->DeleteLocalRef(byteArray);
position++;
}
}
// report update
env->CallStaticVoidMethod(
clazz,
smFields.native_callback_add_cue_point,
thiz,
(jint)reqType, id, type, extra, pos, duration, jkeys, jvalues);
if (jkeys) {
env->DeleteLocalRef(jkeys); // FIX
}
if (jvalues) {
env->DeleteLocalRef(jvalues); // FIX
}
if (jstringClass) {
env->DeleteLocalRef(jstringClass); // FIX
}
if (jbyteArrayClass) {
env->DeleteLocalRef(jbyteArrayClass); // FIX
}
}
You're creating too many local references (two for classes, two for object arrays and some more for strings and byte arrays per run of java_update_cue_point). The VM can only handle a limited amount of local references. See "Referencing Java Objects" for some documentation. The "Global and Local References" section in the JNI function documentation also has some details. The VM will free local references automatically when a native method returns, but in a loop like yours you will have to delete references you don't need anymore with DeleteLocalRef.
To optimize a little, my recommendation would be to do the two FindClass calls outside of the loop in native_update_cue_points and pass them as parameters to java_update_cue_point. This way you'll only have two references for the class objects that the VM will free for you when native_update_cue_points returns, and you save processing time by not finding the same classes over and over again.
I suspect you need to call DeleteLocalRef() on the Java String object created in this line of code:
env->SetObjectArrayElement(jkeys, position, env->NewStringUTF(it->first.c_str()));
Your call to NewStringUTF() creates a Java object with a local reference, which you do not delete.

Create a QML component from string

It is possible to create QML components from files using Qt.createComponent(filename)
It is possible to create QML object from strings using Qt.createQmlObject(string)
It is possible to create QML components from code via Component {...}
But is it possible to create a QML component from a string? I mean without going thorugh the effort of saving it as a temp file just for the sake of using Qt.createComponent(filename)?
EDIT: Just to clarify, I already have the components in this example form:
import QtQuick 2.0
Rectangle {
width: 100
height: 100
color: "red"
}
So I need to create a component from that string without instantiating it. I can't simply wrap the string in a "Component {" + string + "}" because imports can not be declared inside a component. One solution would be to use complex parsing to insert the component just before the first element and after the imports, but it doesn't strike me as the most elegant solution to go about.
Almost 7 years later I've run into the need to do this myself, so I thought I'd elaborate on the comment I left on the question.
For my use case, a singleton does a better job. You can use it in QML like this:
import MyModule 1.0
// ...
Loader {
sourceComponent: QmlComponentCreator.createComponent("import QtQuick 2.0; Item {}")
}
The C++ implementation is below.
QmlComponentCreator.h:
#ifndef QMLCOMPONENTCREATOR_H
#define QMLCOMPONENTCREATOR_H
#include <QObject>
#include <QQmlComponent>
class QmlComponentCreator : public QObject
{
Q_OBJECT
public:
QmlComponentCreator() = default;
Q_INVOKABLE QQmlComponent *createComponent(const QByteArray &data) const;
};
#endif // QMLCOMPONENTCREATOR_H
QmlComponentCreator.cpp:
#include "QmlComponentCreator.h"
#include <QtQml>
QQmlComponent *QmlComponentCreator::createComponent(const QByteArray &data)
{
QQmlComponent *component = new QQmlComponent(qmlEngine(this));
QQmlEngine::setObjectOwnership(component, QQmlEngine::JavaScriptOwnership);
component->setData(data, QUrl());
if (component->isError())
qmlWarning(this) << "Failed to create component from the following data string:\n" << data;
return component;
}
Somewhere in main.cpp, or wherever you register your types:
qmlRegisterSingletonType<QmlComponentCreator>("MyModule", 1, 0, "QmlComponentCreator",
[](QQmlEngine *, QJSEngine *) { return new QmlComponentCreator; });
use Qt.createQmlObject(string). It creates an object, not prototype.
Window {
id: mainWindow
visible: true
width: 600
height: 400
Component.onCompleted: {
var str = '
import QtQuick 2.3;
Component {
Text {
text: "Hello, world!";
anchors.fill: parent;
horizontalAlignment: Text.AlignHCenter;
verticalAlignment: Text.AlignVCenter;
}
}';
var component = Qt.createQmlObject(str,mainWindow);
var object = component.createObject(mainWindow);
}
}

Calling java method contain string as argument and string as return type from c++ in ndk in android

I have one following methods in java:
public native String jniStringMethod();
public String stringMethod(String s) {
Log.d("Testing", "String:" + s);
return s;
}
I am trying to call "stringMethod" method in jniStringMethod() in cpp file in the following way:
jstring Java_ashok_learning_ndk_SampleNDKActivity_jniStringMethod(JNIEnv *env,
jobject obj) {
jstring jstr = env->NewStringUTF("This comes from jni string .");
//jclass clazz = env->GetObjectClass(obj);
jclass clazz = env->FindClass("ashok/learning/ndk/SampleNDKActivity");
if (0 == clazz) {
LOG("clazz class not found!");
}
jmethodID messageMe = env->GetMethodID(clazz, "stringMethod", "(Ljava/lang/String;)Ljava/lang/String;");
if (0 == messageMe) {
LOG("messageMe method not found!");
}
jobject result = env->CallObjectMethod(obj, messageMe, jstr);
LOG("result: %d", result);
const char* str = env->GetStringUTFChars((jstring)result, NULL); // should be released but what a heck, it's a tutorial :)
printf("%s\n", str);
return env->NewStringUTF(str);
}
But it is not getting called..and i am getting log as "messageMe method not found!",means method is not matching with signature...any one can suggest about my mistakes?
Your signture is OK. Are you sure your code is not executed properly even though messageMe is NULL? It has happend to me that my code was running fine despite jmethodID being NULL.

Resources