Related
I have a minimal app made using Marmalade Core. It creates a webview then sends data to be stored and retrieved using s3eSecureStoragePut & s3eSecureStorageGet.
This works fine until I close the app and reopen it. After it loads up again and I try to load the data it returns as blank. Does anyone have any ideas why this might be happening? It is built/deployed using GCC ARM Debug for Android
Here is my c++
#include "s3e.h"
#include "s3eDevice.h"
#include "IwDebug.h"
#include "s3eWebView.h"
#include "IwGx.h"
#include "Iw2D.h"
#include <string>
#include <sstream>
s3eWebView* webView;
bool pageLoaded;
const char* rom = "rom://index.html";
struct SStoreData {
std::string key;
};
static int loadedCallback(s3eWebView* instance, void* sysData, void* userData) {
pageLoaded = true;
return 1;
}
static int javaScriptCallback(s3eWebView* instance, void* systemData, void* userData) {
//cast the data to string for easier handling
std::string data = (char*) systemData;
char jsFunc[128];
//check if page has loaded to prevent errors
if (pageLoaded) {
if (!std::strncmp(data.c_str(), "saveKey", std::strlen("saveKey"))) {
std::string key = data.substr(data.find("|") + 1, data.length());
SStoreData *toSave = new SStoreData;
toSave->key = key;
s3eSecureStoragePut (toSave, sizeof (*toSave));
} else if ("loadKey" == data) {
SStoreData *toLoad = new SStoreData;
s3eSecureStorageGet (toLoad,sizeof(*toLoad));
sprintf(jsFunc, "dataLoaded(\"%s\", \"key\");", toLoad->key.c_str());
s3eWebViewSendJavaScript(webView, jsFunc);
} else {
IwTrace(JS_CALLBACK, ("DID NOT RECEIVE ANYTHING"));
}
}
return 1;
}
void init() {
//init vars
webView = s3eWebViewCreate(false);
pageLoaded = false;
//register callbacks
s3eWebViewRegister(S3E_WEBVIEW_FINISHED_LOADING, loadedCallback, NULL, webView);
s3eWebViewRegister(S3E_WEBVIEW_FROM_JAVASCRIPT, javaScriptCallback, NULL, webView);
//navigate to the webpage
s3eWebViewNavigate(webView, rom);
//display the webview
s3eWebViewShow(webView, 0, 0, IwGxGetDisplayWidth(), IwGxGetDisplayHeight());
}
void destroy() {
if (webView) {
s3eWebViewDestroy(webView);
webView = NULL;
//unregister callbacks
s3eWebViewUnRegister(S3E_WEBVIEW_FINISHED_LOADING, loadedCallback, webView);
s3eWebViewUnRegister(S3E_WEBVIEW_FROM_JAVASCRIPT, javaScriptCallback, webView);
}
delete rom;
//gives me warning so I'm leaving it out.
//delete pipHandler;
}
// Main entry point for the application
int main()
{
//Initialise graphics system(s)
//Initialise graphics system(s)
Iw2DInit();
//Init
init();
// Loop forever, until the user or the OS performs some action to quit the app
while (!s3eDeviceCheckQuitRequest()) {
//Update the input systems
s3eKeyboardUpdate();
s3ePointerUpdate();
// Your rendering/app code goes here.
// Sleep for 0ms to allow the OS to process events etc.
s3eDeviceYield(0);
}
destroy();
//Terminate modules being used
// Return
return 0;
}
and here is the html/js for the webview
<!DOCTYPE html>
<html>
<head>
<title>Storage Spike</title>
</head>
<body onload="main();">
<div>Loaded value:<span id="loadedKey">(loaded value goes here)</span></div>
<input type="text" id="inputKey" value="123key" placeholder="enter key to save"/>
<button id="saveKey">Save</button>
<button id="loadKey">Load</button>
<br>
<script>
function main()
{
var saveKey = document.getElementById("saveKey");
var loadKey = document.getElementById("loadKey");
var inputKey = document.getElementById("inputKey");
saveKey.addEventListener("click", function() {
var key = inputKey.value;
s3e.exec("saveKey|" + key);
});
loadKey.addEventListener("click", function() {
s3e.exec("loadKey");
});
}
function dataLoaded(data, type)
{
console.log(data);
console.log(type);
var loadedKey = document.getElementById("loadedKey");
if(type === "key")
loadedKey.innerHTML = data;
else
console.log("type error");
}
</script>
</body>
</html>
Any help would be greatly appreciated,
Kind Regards
The problem is that you are trying to directly save an instance of std::string.
A std::string instance is nothing but three pointers that describe the memory that it is using to store the actual string data. When the application closes or the memory used to store the data is released, those pointers lose all meaning.
You need to save the actual string data to s3eSecureStorage for it to persist. To do this, you need to serialize the std::string instance. A simple way of doing the serialization is to first save the length of the string, then save the actual string data.
To serialize and save the string to s3eSecureStorage, you could do this:
std::string input = "exampledata";
uint16 inputSize = static_cast<uint16>(input.size());
uint16 bufferSize = static_cast<uint16>(sizeof(uint16) + inputSize);
buffer = new char[bufferSize];
std::memcpy(buffer, &inputSize, sizeof(uint16));
std::memcpy(buffer + sizeof(uint16), input.c_str(), inputSize);
s3eSecureStoragePut(buffer, bufferSize);
delete[] buffer;
And to get the data from s3eSecureStorage and deserialize the string, you could do this:
std::string output;
uint16 dataSize = 0;
s3eSecureStorageGet(&dataSize, sizeof(uint16));
if (dataSize > 0) {
uint16 bufferSize = static_cast<uint16>(dataSize + sizeof(uint16));
char* buffer = new char[bufferSize];
s3eSecureStorageGet(buffer, bufferSize);
output.resize(dataSize);
std::memcpy(&output[0], buffer + sizeof(uint16), dataSize);
delete[] buffer;
}
If you want to store multiple values, the serialization needs to be slightly more complicated but the basic idea is the same.
Obviously you also need to use some kind of error handling for when there is nothing saved to s3eSecureStorage.
The reason the code you posted worked within a single execution of your app is because you allocated the SStoreData instance with new and never released it. When you loaded the pointers back from s3eSecureStorage, they would still point to the allocated memory that held the string data.
Hi I am working on Android JNI Audio part, below jni code I am calling from android 12 times as per my logic. As soon as 1st clip played I send callback event to android and I am again calling selectClip() to play audio clip. I am able to play 2 times but on third call application throwing error as libOpenSLES Leaving BufferQueue : SL_RESULT_BUFFER_INSUFFICIENT.
Am I missing something in this?
Any pointer on the same to resolve this?
jboolean flag=JNI_TRUE;
jint clipNote = 0;
// select the desired clip and play count, and enqueue the first buffer if idle
JNIEXPORT jboolean JNICALL Java_com_example_nativeaudio_NativeAudio_selectClip(JNIEnv * env, jobject obj,
jint count)
{
// sleep logic
while((clock() - t)/1000 < 2000) {
usleep(10000); // sleep for 10ms
}
if(clipNote < 12) {
if(flag == JNI_TRUE) {
__android_log_print(ANDROID_LOG_DEBUG , "CustomTag", " flag = true : ClipNote : %d",clipNote);
clipNote = clipNote + 1;
nextBuffer = (short *) audio1;
nextSize = sizeof(audio1);
nextCount = count/2000;
if (nextSize > 0) {
// here we only enqueue one buffer because it is a long clip,
// but for streaming playback we would typically enqueue at least 2 buffers to start
SLresult result;
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize);
if (SL_RESULT_SUCCESS != result) {
return JNI_FALSE;
}
}
// callback to android
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callBackStart", "(I)V");
if (mid == 0) {
return;
}
flag=JNI_FALSE;
(*env)->CallVoidMethod(env, obj, mid, clipNote);
} else {
// callback to android
__android_log_print(ANDROID_LOG_DEBUG , "CustomTag", " flag = false");
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callBackRelease", "(I)V");
if (mid == 0) {
return;
}
flag = JNI_TRUE;
(*env)->CallVoidMethod(env, obj, mid, count);
}
t = clock();
} else {
SLresult result;
// make sure the asset audio player was created
if (NULL != fdPlayerPlay) {
result = (*fdPlayerPlay)>SetPlayState(fdPlayerPlay,SL_PLAYSTATE_PAUSED);
assert(SL_RESULT_SUCCESS == result);
(void)result;
}
}
return JNI_TRUE;
}
SL_RESULT_BUFFER_INSUFFICIENT error was occurring due to the issue in timing.
Second thread gets started before the first is complete.
The third thread gets started when the first two are in progress. This cannot be handled so it was throwing an error.
The timing was increased sufficient enough to complete the first thread, the issue was solved.
Set numbuffers
Chances are your numbuffers on your SLDataLocator_AndroidSimpleBufferQueue is inappropriately low.
SLDataLocator_AndroidSimpleBufferQueue in_loc;
in_loc.numBuffers = 5; // what number do you have here?
From the OpenSL ES spec:
If the maximum number of buffers specified in the SLDataLocator_BufferQueue structure used as the data source when creating the media object using the CreateAudioPlayer or CreateMidiPlayer method has been reached, the buffer is not added to the buffer queue and SL_RESULT_BUFFER_INSUFFICIENT is returned. At this point the client should wait until it receives a callback notification for a buffer completion at which time it can enqueue the buffer.
Alternatives
If changing your numbuffers to even a very high number doesn't work, ensure that you set your state to playing:
(*player)->SetPlayState( player, SL_PLAYSTATE_PLAYING ); // begin playback
You can call (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); when you stop the player, that will allow the previous thread to shutdown without having to wait on a timer.
(Android, NDK, C++, OpenGL ES)
I need a way to reliably receive the text input from a (soft)keyboard.
The solution can be through Java using a NativeActivity subclass, or anything which works.
At the end I need whatever text is being typed, so I can render it myself with OpenGL
Some background:
Up until now I was triggering the soft keyboard by calling showSoftInput or hideSoftInputFromWindow thought JNI. This never failed so far.
However, the problem is the native activity will not send all characters. Especially some unicode characters outside of ASCII range, or some motion soft keyboard won't work (AKeyEvent_getKeyCode)
It used to be possible to get some of those other unicode characters why checking for KeyEvent.ACTION_MULTIPLE and reading a string of characters.
But even this won't work reliably anymore.
So far I failed to find an alternative method.
I experimented with programmatically adding a EditText, but never got it to work. Even trying to add a simple Button resulted in the OpenGL view to no longer being rendered.
On iOS I worked around it by having a hiding edit box, which I simply activated to make the keyboard show up. I would then read out the edit box and use the string to render myself in OpenGL.
I have the same issues, and I have solved it using a 'Character' event that I process separately from the InputEvent.
The problem is this: AKeyEvent_getKeyCode doesn't return the KeyCode for some softkey events, notably the expanded 'unicode/latin' characters when you hold down a key. This prevents the methods #Shammi and #eozgonul from working because the KeyEvent reconstructed on the Java side doesn't have enough information to get a unicode character.
Another issue is that the InputQueue is drained on the C++/Native side before the dispatchKeyEvent event(s) are fired. This means that the KEYDOWN/KEYUP events all fired before the Java code can process the events. (They are not interleaved).
My solution is to capture the unicode characters on the Java side by overriding dispatchKeyEvent and sending the characters to a Queue<Integer> queueLastInputCharacter = new ConcurrentLinkedQueue<Integer>();
// [JAVA]
#Override
public boolean dispatchKeyEvent (KeyEvent event)
{
int metaState = event.getMetaState();
int unichar = event.getUnicodeChar(metaState);
// We are queuing the Unicode version of the characters for
// sending to the app during processEvents() call.
// We Queue the KeyDown and ActionMultiple Event UnicodeCharacters
if(event.getAction()==KeyEvent.ACTION_DOWN){
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else{
unichar = event.getUnicodeChar();
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else if (event.getDisplayLabel() != 0){
String aText = new String();
aText = "";
aText += event.getDisplayLabel();
queueLastInputCharacter.offer(Integer.valueOf(Character.codePointAt(aText, 0)));
}
else
queueLastInputCharacter.offer(Integer.valueOf(0));
}
}
else if(event.getAction()==KeyEvent.ACTION_MULTIPLE){
unichar = (Character.codePointAt(event.getCharacters(), 0));
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
return super.dispatchKeyEvent(event);
}
The concurrent queue is going to let the threads play nice together.
I have a Java side method that returns the last input character:
// [JAVA]
public int getLastUnicodeChar(){
if(!queueLastInputCharacter.isEmpty())
return queueLastInputCharacter.poll().intValue();
return 0;
}
At the end of my looper code, I tacked on an extra check to see if the queue retained any unicode characters:
// [C++]
int ident;
int events;
struct android_poll_source* source;
// If not rendering, we will block 250ms waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(((nv_app_status_focused(_lpApp)) ? 1 : 250),
NULL,
&events,
(void**)&source)) >= 0)
{
// Process this event.
if (source != NULL)
source->process(_lpApp, source);
// Check if we are exiting. If so, dump out
if (!nv_app_status_running(_lpApp))
return;
}
static int modtime = 10; // let's not run on every call
if(--modtime == 0) {
long uniChar = androidUnicodeCharFromKeyEvent();
while (uniChar != 0) {
KEvent kCharEvent; // Game engine event
kCharEvent.ptkKey = K_VK_ERROR;
kCharEvent.unicodeChar = uniChar;
kCharEvent.character = uniChar;
/* Send unicode char */
kCharEvent.type = K_EVENT_UNICHAR;
_lpPortableHandler(&kCharEvent);
if (kCharEvent.character < 127) {
/* Send ascii char for source compatibility as well */
kCharEvent.type = K_EVENT_CHAR;
_lpPortableHandler(&kCharEvent);
}
uniChar = androidUnicodeCharFromKeyEvent();
}
modtime = 10;
}
The androidUnicodeCharFromKeyEvent function is very similar to #Shammi 's GetStringFromAInputEvent method, only use CallIntMethod to return the jint.
Notes
This does require modifying your engine to process character events separate from Key events. Android still has key codes like AKEYCODE_BACK or AKEYCODE_ENTER that are not character events and still need to be handled (and can be handled on the main input looper).
Editboxes, consoles, etc... Things that are expecting user input can be modified to receive a separate character event that builds the string. If you are working on multiple platforms, then you will need to generate these new character events in addition to the normal key input events.
I hope this works for you, worked for me so far.
int GetUnicodeChar(struct android_app* app, int eventType, int keyCode, int metaState)
{
JavaVM* javaVM = app->activity->vm;
JNIEnv* jniEnv = app->activity->env;
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = "NativeThread";
attachArgs.group = NULL;
jint result = javaVM->AttachCurrentThread(&jniEnv, &attachArgs);
if(result == JNI_ERR)
{
return 0;
}
jclass class_key_event = jniEnv->FindClass("android/view/KeyEvent");
int unicodeKey;
if(metaState == 0)
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "()I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char);
}
else
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "(I)I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char, metaState);
}
javaVM->DetachCurrentThread();
LOGI("Unicode key is: %d", unicodeKey);
return unicodeKey;
}
Just call it from your input handler, my structure is approximately as follows:
switch (AInputEvent_getType(event))
{
case AINPUT_EVENT_TYPE_KEY:
switch (AKeyEvent_getAction(event))
{
case AKEY_EVENT_ACTION_DOWN:
int key = AKeyEvent_getKeyCode(event);
int metaState = AKeyEvent_getMetaState(event);
int uniValue;
if(metaState != 0)
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, metaState);
else
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, 0);
Since you stated that you already open the soft keyboard, I don't go into that part but the code is kind of straight forward. I basically use the Java function of class KeyEvent which has GetUnicodeChar function.
Eozgonul's solution worked for me. I adopted it and modified it to split the work between Java and the native side. Basically I extend NativeActivity to derive my own class which allows me to move as much as possible to Java. I also ended up passing all the data from the input event. I wanted to make sure I captured as much as possible in the created KeyEvent object.
package com.MyCompany.MyApp;
import android.os.Bundle;
import android.view.inputmethod.InputMethodManager;
import android.content.Context;
import android.view.KeyEvent;
public class MyNativeActivity extends android.app.NativeActivity
{
// Need this for screen rotation to send configuration changed callbacks to native
#Override
public void onConfigurationChanged( android.content.res.Configuration newConfig )
{
super.onConfigurationChanged( newConfig );
}
public void showKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.showSoftInput( this.getWindow().getDecorView(), InputMethodManager.SHOW_FORCED );
}
public void hideKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.hideSoftInputFromWindow( this.getWindow().getDecorView().getWindowToken(), 0 );
}
public String stringFromKeyCode( long downTime, long eventTime,
int eventAction, int keyCode, int repeatCount, int metaState,
int deviceId, int scanCode, int flags, int source )
{
String strReturn;
KeyEvent keyEvent = new KeyEvent( downTime, eventTime, eventAction, keyCode, repeatCount, metaState, deviceId, scanCode, flags, source );
if ( metaState == 0 )
{
int unicodeChar = keyEvent.getUnicodeChar();
if ( eventAction == KeyEvent.ACTION_MULTIPLE && unicodeChar == keyEvent.KEYCODE_UNKNOWN )
{
strReturn = keyEvent.getCharacters();
}
else
{
strReturn = Character.toString( ( char )unicodeChar );
}
}
else
{
strReturn = Character.toString( ( char )( keyEvent.getUnicodeChar( metaState ) ) );
}
return strReturn;
}
}
On the native side...
std::string GetStringFromAInputEvent( android_app* pApp, AInputEvent* pInputEvent )
{
std::string strReturn;
JavaVM* pJavaVM = pApp->activity->vm;
JNIEnv* pJNIEnv = pApp->activity->env;
JavaVMAttachArgs javaVMAttachArgs;
javaVMAttachArgs.version = JNI_VERSION_1_6;
javaVMAttachArgs.name = "NativeThread";
javaVMAttachArgs.group = NULL;
jint jResult;
jResult = pJavaVM->AttachCurrentThread( &pJNIEnv, &javaVMAttachArgs );
if ( jResult != JNI_ERR )
{
// Retrieves NativeActivity.
jobject nativeActivity = pNativeActivity->clazz;
jclass ClassNativeActivity = pJNIEnv->GetObjectClass( nativeActivity );
jmethodID MethodStringFromKeyCode = pJNIEnv->GetMethodID( ClassNativeActivity, "stringFromKeyCode", "(JJIIIIIIII)Ljava/lang/String;" );
jlong jDownTime = AKeyEvent_getDownTime( pInputEvent );
jlong jEventTime = AKeyEvent_getEventTime( pInputEvent );
jint jEventAction = AKeyEvent_getAction( pInputEvent );
jint jKeyCode = AKeyEvent_getKeyCode( pInputEvent );
jint jRepeatCount = AKeyEvent_getRepeatCount( pInputEvent );
jint jMetaState = AKeyEvent_getMetaState( pInputEvent );
jint jDeviceID = AInputEvent_getDeviceId( pInputEvent );
jint jScanCode = AKeyEvent_getScanCode( pInputEvent );
jint jFlags = AKeyEvent_getFlags( pInputEvent );
jint jSource = AInputEvent_getSource( pInputEvent );
jstring jKeyCodeString = ( jstring )pJNIEnv->CallObjectMethod( nativeActivity, MethodStringFromKeyCode,
jDownTime, jEventTime, jEventAction,
jKeyCode, jRepeatCount, jMetaState,
jDeviceID, jScanCode, jFlags, jSource );
const char* keyCodeString = pJNIEnv->GetStringUTFChars( keyCodeString, nullptr );
strReturn = std::string( keyCodeString );
pJNIEnv->ReleaseStringUTFChars( jKeyCodeString, keyCodeString );
// Finished with the JVM.
pJavaVM->DetachCurrentThread();
}
return strReturn;
}
The 2 reasons I went with this approach..
Reduces code syntax complexity by moving code to java and only having you to call one jni wrapper method on the native side.
Java is the preferred Android language and this allows me to quickly iterate on java based solutions. Moreover most existing solutions are in java.
Basically this will solve the issue.
NativeActivity override onKeyDown()
But you'll have to implement some other way than the NDK key input to get the onKeyMultiple's event.getCharacters() string into your code.
I am looking since some time for a way to play MIDI in Delphi XE5 with Android targeted. Several of my questions before were related to this "quest" :-). I have filed two requests to embarcadero: #119422 to add MIDI support to TMediaPlayer and #119423 to add a MIDI framework to Firemonkey, but that did not help. I have succeeded at last. As I know there are some more people who were looking for MIDI on Android I post this question with answer for documentation.
The Android system has an internal MIDI synthesizer. You can access it via the Android NDK. I have described this in an article containing some downloads. This answer is a short description of this article. What you'll see here is a Proof of Concept. It will show how to play MIDI notes on an Android system but needs improvement. Suggestions for improvement are welcome :-)
Use Eclipse to interface with the Java project. I presume you have Delphi XE5 with the Mobile pack, which gives you two things already installed: the Android SDK and NDK. Do not reinstall these by downloading the complete Android SDK from Google. Download and install the Eclipse Android Development Tools (ADT) plugin and follow the installation instructions. This allows you to use the Android SDK/NDK environment already installed by Delphi XE5 (you will find the paths in Delphi, Options | Tools | SDK Manager). In this way Delphi and Eclipse will share the same SDK and NDK.
I used the MIDI library developed by Willam Farmer. He also has the full SoniVox documentation available which I couldn't get elsewhere. His driver comes with a full example (Java) program. I created my own project with and changed the package name to org.drivers.midioutput, so all functions are prefixed by Java_org_drivers_midioutput_MidiDriver_ (see code below).
When you wish to compile the midi.c jus open a command window, and call ndk-build in the project directory. Some error messages are ok. The mips and x86 libraries were not built in my case.
There is one point though you should be aware of: the path to the ndk may not contain spaces. As you let the Delphi installer install Delphi there is bound to be a space in it: the subdirectory Rad Studio in that terrible long file name where Delphi installs the SDK and NDK. In order to work around this problem, create an empty directory on drive C:, call it C:\ndk. Use MKLINK to link this directory to the ndk directory. This can only be done from an elevated command prompt and as you do so you'll lose your network connections. The link is persistent so just close the command prompt and open another, unelevated one, and all should work now. Now you can really use ndk-build.
midi.c - the NDK interface with the SoniVox
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
// Some slight modifications by Arnold Reinders. Added a test function and changed
// the package to org.drivers.midioutput. The original copyright still applies
#include
// for EAS midi
#include "eas.h"
#include "eas_reverb.h"
// determines how many EAS buffers to fill a host buffer
#define NUM_BUFFERS 4
// EAS data
static EAS_DATA_HANDLE pEASData;
const S_EAS_LIB_CONFIG *pLibConfig;
static EAS_PCM *buffer;
static EAS_I32 bufferSize;
static EAS_HANDLE midiHandle;
// This function is added to test whether the functionality of this NDK code can be accesses
// without needing to access the MIDI system. Added for testing purposes
jint
Java_org_drivers_midioutput_MidiDriver_version (JNIEnv *env, jobject clazz)
{
return 3;
}
// init EAS midi
jint
Java_org_drivers_midioutput_MidiDriver_init(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
// get the library configuration
pLibConfig = EAS_Config();
if (pLibConfig == NULL || pLibConfig->libVersion != LIB_VERSION)
return 0;
// calculate buffer size
bufferSize = pLibConfig->mixBufferSize * pLibConfig->numChannels *
NUM_BUFFERS;
// init library
if ((result = EAS_Init(&pEASData)) != EAS_SUCCESS)
return 0;
// select reverb preset and enable
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_PRESET,
EAS_PARAM_REVERB_CHAMBER);
EAS_SetParameter(pEASData, EAS_MODULE_REVERB, EAS_PARAM_REVERB_BYPASS,
EAS_FALSE);
// open midi stream
if (result = EAS_OpenMIDIStream(pEASData, &midiHandle, NULL) !=
EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return 0;
}
return bufferSize;
}
// midi config
jintArray
Java_org_drivers_midioutput_MidiDriver_config(JNIEnv *env,
jobject clazz)
{
jboolean isCopy;
if (pLibConfig == NULL)
return NULL;
jintArray configArray = (*env)->NewIntArray(env, 4);
jint *config = (*env)->GetIntArrayElements(env, configArray, &isCopy);
config[0] = pLibConfig->maxVoices;
config[1] = pLibConfig->numChannels;
config[2] = pLibConfig->sampleRate;
config[3] = pLibConfig->mixBufferSize;
(*env)->ReleaseIntArrayElements(env, configArray, config, 0);
return configArray;
}
// midi render
jint
Java_org_drivers_midioutput_MidiDriver_render(JNIEnv *env,
jobject clazz,
jshortArray shortArray)
{
jboolean isCopy;
EAS_RESULT result;
EAS_I32 numGenerated;
EAS_I32 count;
jsize size;
// jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy)
// void ReleaseByteArrayElements(jbyteArray array, jbyte* elems,
// void* GetPrimitiveArrayCritical(JNIEnv*, jarray, jboolean*);
// void ReleasePrimitiveArrayCritical(JNIEnv*, jarray, void*, jint);
if (pEASData == NULL)
return 0;
buffer =
(EAS_PCM *)(*env)->GetShortArrayElements(env, shortArray, &isCopy);
size = (*env)->GetArrayLength(env, shortArray);
count = 0;
while (count < size) { result = EAS_Render(pEASData, buffer + count, pLibConfig->mixBufferSize, &numGenerated);
if (result != EAS_SUCCESS)
break;
count += numGenerated * pLibConfig->numChannels;
}
(*env)->ReleaseShortArrayElements(env, shortArray, buffer, 0);
return count;
}
// midi write
jboolean
Java_org_drivers_midioutput_MidiDriver_write(JNIEnv *env,
jobject clazz,
jbyteArray byteArray)
{
jboolean isCopy;
EAS_RESULT result;
jint length;
EAS_U8 *buf;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
buf = (EAS_U8 *)(*env)->GetByteArrayElements(env, byteArray, &isCopy);
length = (*env)->GetArrayLength(env, byteArray);
result = EAS_WriteMIDIStream(pEASData, midiHandle, buf, length);
(*env)->ReleaseByteArrayElements(env, byteArray, buf, 0);
if (result != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
// shutdown EAS midi
jboolean
Java_org_drivers_midioutput_MidiDriver_shutdown(JNIEnv *env,
jobject clazz)
{
EAS_RESULT result;
if (pEASData == NULL || midiHandle == NULL)
return JNI_FALSE;
if ((result = EAS_CloseMIDIStream(pEASData, midiHandle)) != EAS_SUCCESS)
{
EAS_Shutdown(pEASData);
return JNI_FALSE;
}
if ((result = EAS_Shutdown(pEASData)) != EAS_SUCCESS)
return JNI_FALSE;
return JNI_TRUE;
}
When the library is built by ndk-build this will prefix the compiled library with lib and replace the extension by .so. So midi.c will compile to libmidi.so. Compiled libraries are added to the download, so you needn't compile midi.c.
MidiDriver.Java declares an interface, an audioTrack and a thread to handle all these. I haven't taken the trouble to find how exactly this works. Because I didn't know how to handle an interface and such in Delphi I created a Java wrapper for MidiDriver: class MIDI_Output. This class is used to interface with Delphi.
Class MidiDriver is the interface between Java and the C-functions that call SoniVox functions. Class MIDI_Output is the interface between Java and Delphi. MIDI_Output creates an instance of MidiDriver.
Class MidiDriver - the interface with the NDK
////////////////////////////////////////////////////////////////////////////////
//
// MidiDriver - An Android Midi Driver.
//
// Copyright (C) 2013 Bill Farmer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Bill Farmer william j farmer [at] yahoo [dot] co [dot] uk.
//
///////////////////////////////////////////////////////////////////////////////
package org.drivers.midioutput;
import java.io.File;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
// MidiDriver
public class MidiDriver implements Runnable
{
private static final int SAMPLE_RATE = 22050;
private static final int BUFFER_SIZE = 4096;
private Thread thread;
private AudioTrack audioTrack;
private OnMidiStartListener listener;
private short buffer[];
// Constructor
public MidiDriver ()
{
Log.d ("midi", " *** MidiDriver started");
}
public void start ()
{
// Start the thread
thread = new Thread (this, "MidiDriver");
thread.start ();
} // start //
#Override
public void run ()
{
processMidi ();
} // run //
public void stop ()
{
Thread t = thread;
thread = null;
// Wait for the thread to exit
while (t != null && t.isAlive ())
Thread.yield ();
} // stop //
// Process MidiDriver
private void processMidi ()
{
int status = 0;
int size = 0;
// Init midi
Log.d ("midi", " *** processMIDI");
if ((size = init()) == 0)
return;
buffer = new short [size];
// Create audio track
audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE,
AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE, AudioTrack.MODE_STREAM);
if (audioTrack == null)
{
shutdown ();
return;
} // if
// Call listener
if (listener != null)
listener.onMidiStart();
// Play track
audioTrack.play();
// Keep running until stopped
while (thread != null)
{
// Render the audio
if (render (buffer) == 0)
break;
// Write audio to audiotrack
status = audioTrack.write (buffer, 0, buffer.length);
if (status < 0) break; } // while // Render and write the last bit of audio if (status > 0)
if (render(buffer) > 0)
audioTrack.write(buffer, 0, buffer.length);
// Shut down audio
shutdown();
audioTrack.release();
} // processMidi //
public void setOnMidiStartListener (OnMidiStartListener l)
{
listener = l;
} // setOnMidiStartListener //
public static void load_lib (String libName)
{
File file = new File (libName);
if (file.exists ())
{
System.load (libName);
} else
{
System.loadLibrary (libName);
}
} // Listener interface
public interface OnMidiStartListener
{
public abstract void onMidiStart ();
} // OnMidiStartListener //
// Native midi methods
public native int version ();
private native int init ();
public native int [] config ();
private native int render (short a []);
public native boolean write (byte a []);
private native boolean shutdown ();
// Load midi library
static
{
System.loadLibrary ("midi");
}
}
Class MIDI_Output - providing a wrap for class MidiDriver
package org.drivers.midioutput;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import org.drivers.midioutput.MidiDriver.OnMidiStartListener;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.os.Environment;
import android.util.Log;
public class MIDI_Output implements OnMidiStartListener
{
protected MidiDriver midi_driver;
protected MediaPlayer media_player;
public MIDI_Output ()
{
// Create midi driver
midi_driver = new MidiDriver();
Log.d ("midi", " *** midi_driver opened with version " +
String.valueOf (midi_driver.version ()));
// Set onmidistart listener to this class
if (midi_driver != null)
midi_driver.setOnMidiStartListener (this);
} // MIDI_Output () //
public int test_int (int n)
{
int sq = n * n;
// Log.d ("midi", " *** test_int computes " + String.valueOf (sq));
return n * n;
}
public void start ()
{
if (midi_driver != null)
{
midi_driver.start ();
Log.d ("midi", " *** midi_driver.start ()");
}
} // start //
public void stop ()
{
if (midi_driver != null)
{
midi_driver.stop ();
Log.d ("midi", " *** midi_driver.stop ()");
}
stopSong ();
} // stop //
// Listener for sending initial midi messages when the Sonivox
// synthesizer has been started, such as program change. Runs on
// the MidiDriver thread, so should only be used for sending midi
// messages.
#Override
public void onMidiStart()
{
Log.d ("midi", " *** onSMidiStart");
// TODO
}
// Sends a midi message
protected void putShort (int m, int n, int v)
{
if (midi_driver != null)
{
byte msg [] = new byte [3];
msg [0] = (byte) m;
msg [1] = (byte) n;
msg [2] = (byte) v;
Log.d ("midi", " *** putShort (" + String.valueOf (m) + ", " + String.valueOf (n) + ", " + String.valueOf (v) + ")");
midi_driver.write (msg);
} // if
} // putShort //
public boolean isPlayingSong ()
{
return media_player != null;
} // isPlayingSong //
public void playSong (String audioFilename)
{
String audioPath;
try
{
FileDescriptor fd = null;
audioFilename = "/Data/d/song.mid";
File baseDir = Environment.getExternalStorageDirectory ();
audioPath = baseDir.getAbsolutePath () + audioFilename;
Log.d ("midi", " *** Look for file: " + audioPath);
FileInputStream fis = new FileInputStream (audioPath);
fd = fis.getFD ();
if (fd != null)
{
Log.d ("midi", " *** Found file, trying to play: " + audioPath);
MediaPlayer mediaPlayer = new MediaPlayer ();
mediaPlayer.setDataSource (fd);
mediaPlayer.prepare ();
mediaPlayer.start ();
}
} catch (Exception e)
{
Log.d ("midi", " *** Exception while trying to play file: " + e.getMessage ());
}
}
public void stopSong ()
{
if (media_player != null)
{
media_player.stop ();
media_player.release ();
media_player = null;
} // if
} // stopSong //
} // Class: MIDI_Output //
From MidiDriver and MIDI_Output an Eclipse Android project was created, a MainActivity added and run. After eliminating a lot of bugs I got it up and running. A useful tool is the android debugger (adb). Open a command windows and run adb -d logcat. I have added a lot of log.d ('midi", " *** message') statements in the code in order to see where things went wrong. Remove them if you don't like them, but if you are unknown to Android (which I still am to a great extent) it is a useful way to see what happens in your application. Log works as wel in Delphi, see the Delphi Sources.
When the program compiles well you have a MIDI_Output.apk package in your project\bin directory. This package will be used by Delphi to run the Java methods.
Java can be accessed from Delphi by using JNI. A hands-on tutorial can be found on the site of RedTitan. The ideas of this tutorial were implemented in class TMIDI_Output_Device.
As you may see a constant string test_apk_fn is defined with the path to the MIDI_Output.apk Android package. This string provides JNI with the name where the Java library can be found. The string javaClassName provides the package name necessary to interface with Java. With these string the Delphi JNI is able to find the requested classes.
Class TMIDI_Output_Device - providing a Delphi wrap for Java class MIDI_Output
unit MIDI_Output_Device;
interface
uses
System.SysUtils,
FMX.Types,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes,
Androidapi.Jni,
Androidapi.JNI.Dalvik,
Androidapi.JNI.GraphicsContentViewText;
const
test_apk_fn = '/storage/sdcard0/Data/d/MIDI_Output.apk';
type
TMIDI_Output_Device = class (TObject)
private
JavaEnv: PJNIEnv;
context: JContext;
CL: JDexClassLoader;
JavaObject: JObject;
JavaObjectID: JNIObject;
jTempClass: Jlang_Class;
jTemp: JObject;
oTemp: TObject;
jLocalInterface: ILocalObject;
optimizedpath_jfile: JFile;
dexpath_jstring, optimizedpath_jstring: JString;
fun_version: JNIMethodID;
fun_start: JNIMethodID;
fun_put_short: JNIMethodID;
fun_play_song: JNIMethodID;
public
constructor Create;
procedure setup_midi_output (class_name: string);
procedure put_short (status, data_1, data_2: integer);
procedure play_song (file_name: string);
end; // Class: MIDI_Output_Device //
implementation
uses
FMX.Helpers.Android;
constructor TMIDI_Output_Device.Create;
begin
setup_midi_output ('MIDI_Output');
end; // Create //
procedure TMIDI_Output_Device.setup_midi_output (class_name: string);
var
javaClassName: string;
ji: JNIInt;
jiStatus, jiData_1, jiData_2: JNIValue;
begin
javaClassName := Format ('org.drivers.midioutput/%s', [class_name]);
context := SharedActivityContext;
JavaEnv := TJNIResolver.GetJNIEnv;
Log.d ('Loading external library from "' + test_apk_fn + '"');
dexpath_jstring := StringToJString (test_apk_fn);
// locate/create a directory where our dex files can be put
optimizedpath_jfile := context.getDir (StringToJString ('outdex'), TJContext.javaclass.mode_private);
optimizedpath_jstring := optimizedpath_jfile.getAbsolutePath;
Log.d ('Path for DEX files = ' + JStringToString (optimizedpath_jstring));
Log.d ('APK containing target class = ' + JStringToString (dexpath_jstring));
CL := TJDexClassLoader.JavaClass.init (dexpath_jstring, optimizedpath_jstring, nil, TJDexClassLoader.JavaClass.getSystemClassLoader);
// Test whether the Dex class is loaded, if not, exit
if not assigned (CL) then
begin
Log.d ('?Failed to get DEXClassLoader');
exit;
end; // if
// Load the Java class
jTempClass := CL.loadClass (StringToJString (javaClassName));
if assigned (jTempClass) then
begin
jTemp := jTempClass; // N.B You could now import the entire class
if jTemp.QueryInterface (ILocalObject,jLocalInterface) = S_OK then
begin
// supports ilocalobject
JavaObject := jTempClass.newInstance;
oTemp := JavaObject as TObject;
JavaObjectID := tjavaimport (otemp).GetObjectID;
Log.d (oTemp.ClassName);
// try to access the version function from the midi_output class
fun_version := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'version', '()I');
if not assigned (fun_version) then
begin
Log.d ('?fun_version not supported');
end else
begin
ji := JavaEnv^.CallIntMethodA (JavaEnv, JavaObjectID, fun_version, nil);
Log.d ('version returns ' + inttostr (ji));
end; // if
// try to access the start function from the midi_output class
fun_start := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'start', '()V');
if not assigned (fun_start) then
begin
Log.d ('?fun_start not supported');
end else
begin
JavaEnv^.CallVoidMethodA (JavaEnv, JavaObjectID, fun_start, nil);
Log.d ('fun_start found');
end; // if
// try to access the putShort function from the midi_output class
fun_put_short := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'putShort','(III)V');
if not assigned (fun_put_short) then
begin
Log.d ('?putShort not supported');
end else
begin
Log.d (Format (' ### putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
put_short ($90, 60, 127);
end; // if
// try to access the playSong function from the midi_output class
fun_play_song := TJNIResolver.GetJavaMethodID ((jTempClass as ILocalObject).GetObjectID, 'playSong', '(Ljava/lang/String)V');
if not assigned (fun_play_song) then
begin
Log.d ('?playSong not supported');
end else
begin
Log.d (' ### playSong found');
end; // if
end else
begin
Log.d ('?Could not derive ILOCALOBJECT');
end;
end else Log.d ('?'+javaClassname+' not found')
end; // setup_midi_output //
procedure TMIDI_Output_Device.put_short (status, data_1, data_2: integer);
var
jiStatus, jiData_1, jiData_2: JNIValue;
x: array of JNIOBJECT;
begin
jiStatus.i := status;
jiData_1.i := data_1;
jiData_2.i := data_2;
setLength (x, 3);
x [0] := jiStatus.l;
x [1] := jiData_1.l;
x [2] := jiData_2.l;
Log.d (Format ('putShort (%d, %d, %d)', [jiStatus.i, jiData_1.i, jiData_2.i]));
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_put_short, x);
end; // put_short //
procedure TMIDI_Output_Device.play_song (file_name: string);
var
x: array of JNIObject;
begin
SetLength (x, 1);
x [0] := StringToJNIString (JavaEnv, file_name);
Log.d ('playSong (' + file_name + ')');
JavaEnv^.CallVoidMethodV (JavaEnv, JavaObjectID, fun_play_song, x);
end; // playSong //
end. // Unit: MIDI_Output_Device //
Delphi now knows where to find the Java classes. In theory it should now be able to find libmidi.so because an Android package is a .zip file containing the necessary files to run the Java package. If you open MIDI_Output.apk with WinZip or WinRar then you see these files. In the archive you'll find a directory lib which contains libmidi.so for the ARM 5 and 7 platforms. When launching the program and having adb -d logcat running in a command window adb says so much as unpacking MIDI_Output.apk. Well, it might do so, but libmidi.so will not be found.
Libmidi.so should be added to the \usr\lib somewhere under the \platforms directory of the Android SDK. The complete link in my case is: C:\Users\Public\Documents\RAD Studio\12.0\PlatformSDKs\android-ndk-r8e\platforms\android-14\arch-arm\usr\lib. This should help as I found out some time ago.
Using the call chain as I have shown here one may call MIDI functions in Delphi generated Android code. There are some questions regarding this technique:
Wouldn't it be easier to call the NDK function directly? It is
possible to call NDK functions directly from Delphi in the same
way as DLL's. However, class MidiDriver adds a lot of functionality
which I do not understand at this moment. This functionality must be
programmed in C or Pascal when call the NDK functions directly.
In the code from Bill Farmer he uses the MediaPlayer to play MIDI
files. Alas the MediaPlayer can only be accessed from an Activity and
I do not know how to transfer the Delphi MainActivity to a JNI Java
function. So this functionality does not work as of yet.
Native libraries are packed into the .apk but not unpacked in such a
way that the JavaVM detects it. Now the libmidi.so has to be put
manually into \usr\lib.
Even worse is that a hard link must be added to the .apk package. The
package should be deployed automatically to the /data/app-lib of the
application, else creating an app with JNI classes and installing it
from the Play Store seems impossible.
Another way would have been to use a native Android version of BASS plus BASSMIDI plugin. There is good sample code coming with it. And Ian's support is excellent. You can find both of them here:
BASS lib for Android: http://www.un4seen.com/forum/?topic=13225
BASSMIDI plugin (d/l): http://www.un4seen.com/download.php?bassmidi24-linux
There is a .NET version on his site as well, and a 3rd party project on Sourceforge that exposes the API to Java. I'm not allowed to post more than two links (yet) but you can find it with a quick search for nativebass
Fairly late answer but it might still help someone else looking for a quicker way or one that works in Delphi alone.
I'm working on an audio streamer, and I want to be able to modify the file I'm streaming, and also the target I'm streaming to. To do this I would modify the location for my filesrc, or I would modify the host/port of my udpsink.
I am having trouble understanding everything I need to know to get this pipeline linked together and playing. Previously I hard coded everything and used the gst pipeline parsing tool with this pipeline:
filesrc location=/storage/sdcard0/Music/RunToTheHills.ogg ! oggdemux ! vorbisdec ! audioresample ! audioconvert ! audio/x-raw-int,channels=2,depth=16,width=16,rate=44100 ! rtpL16pay ! udpsink host=192.168.100.126 port=9001
Now I want to change the filesrc location, and udp host/port as mentioned above.
My application is an Android app using the NDK. However, this should not effect the code needed to set up a pipeline.
Here's what I've got so far, which results in a segfault.
My data structure:
/**
* Structure to hold all the various variables we need.
* This is handed to callbacks
*/
typedef struct _CustomData {
jobject app; /* The Java app */
GstElement *pipeline; /* gStreamer pipeline */
GstElement *filesrc; /* Input file */
GstPad *fileblock; /* Used to block filesrc */
GstElement *ogg; /* Ogg demultiplexer */
GstElement *vorbis; /* Vorbis decoder */
GstElement *resample;
GstElement *convert;
GstCaps *caps;
GstElement *rtp; /* RTP packer */
GstElement *udp; /* UDP sender */
GMainContext *context; /* GLib Context */
GMainLoop *main_loop; /* GLib main loop */
gboolean initialised; /* True after initialisation */
GstState state; /* Pipeline state */
GstState target_state; /* What state we want to put the pipeline into */
gint64 duration; /* Clip length */
gint64 desired_position; /* Where we want to track to within the clip */
GstClockTime last_seek_time; /* Used to throttle seeking */
gboolean is_live; /* Live streams don't need buffering */
} CustomData;
And here's my creation of the pipeline:
data->pipeline = gst_pipeline_new("pipeline");
data->filesrc = gst_element_factory_make("filesrc", NULL);
if (!data->filesrc) {
GST_ERROR("Failed to create filesrc.");
return NULL;
}
g_object_set(G_OBJECT(data->filesrc), "location", "/storage/sdcard0/Music/RunToTheHills.ogg", NULL);
data->fileblock = gst_element_get_static_pad(data->filesrc, "src");
data->ogg = gst_element_factory_make("oggdemux", NULL);
if (!data->ogg) {
GST_ERROR("Failed to create oggdemux.");
return NULL;
}
data->vorbis = gst_element_factory_make("vorbisdec", NULL);
if (!data->vorbis) {
GST_ERROR("Failed to create vorbisdec.");
return NULL;
}
data->resample = gst_element_factory_make("audioresample", NULL);
if (!data->resample) {
GST_ERROR("Failed to create audioresample.");
return NULL;
}
data->convert = gst_element_factory_make("audioconvert", NULL);
if (!data->convert) {
GST_ERROR("Failed to create audioconvert.");
return NULL;
}
data->caps = gst_caps_new_simple("audio/x-raw-int",
"channels", G_TYPE_INT, 2,
"depth", G_TYPE_INT, 16,
"width", G_TYPE_INT, 16,
"rate", G_TYPE_INT, 44100);
if (!data->caps) {
GST_ERROR("Failed to create caps");
return NULL;
}
data->rtp = gst_element_factory_make("rtpL16pay", NULL);
if (!data->rtp) {
GST_ERROR("Failed to create rtpL16pay.");
return NULL;
}
data->udp = gst_element_factory_make("udpsink", NULL);
if (!data->udp) {
GST_ERROR("Failed to create udpsink.");
return NULL;
}
g_object_set(G_OBJECT(data->udp), "host", "192.168.100.126", NULL);
g_object_set(G_OBJECT(data->udp), "port", 9001, NULL);
if (!data->ogg || !data->vorbis || !data->resample || !data->convert || !data->caps || !data->rtp || !data->udp) {
GST_ERROR("Unable to create all elements!");
return NULL;
}
gst_bin_add_many(GST_BIN(data->pipeline), data->filesrc, data->ogg, data->vorbis,
data->resample, data->convert, data->caps, data->rtp, data->udp);
/* Link all the elements together */
gst_element_link(data->filesrc, data->ogg);
gst_element_link(data->ogg, data->vorbis);
gst_element_link(data->vorbis, data->resample);
gst_element_link(data->resample, data->convert);
gst_element_link_filtered(data->convert, data->rtp, data->caps);
gst_element_link(data->rtp, data->udp);
Can someone give me some hints as to where I went wrong?
For interest, here's my previously working pipeline:
data->pipeline = gst_parse_launch("filesrc location=/storage/sdcard0/Music/RunToTheHills.ogg ! oggdemux ! vorbisdec ! audioresample ! audioconvert ! audio/x-raw-int,channels=2,depth=16,width=16,rate=44100 ! rtpL16pay ! udpsink host=192.168.100.126 port=9001", &error);
if (error) {
gchar *message = g_strdup_printf("Unable to build pipeline: %s", error->message);
g_clear_error (&error);
set_ui_message(message, data);
g_free (message);
return NULL;
}
You cannot simply link the oggdemux to the vorbisdec, because the demux has sometimes pads.
You need to add a handler function for the 'pad-added' signal of the demux and then perform the link there.
/* Connect to the pad-added signal */
g_signal_connect (data->ogg, "pad-added", G_CALLBACK (pad_added_handler), data);
And the handler:
void on_pad_added (GstElement *src, GstPad *new_pad, CustomData *data)
{
GstPad *sink_pad = gst_element_get_static_pad (data->vorbis, "sink");
GstPadLinkReturn ret;
GstCaps *new_pad_caps = NULL;
GstStructure *new_pad_struct = NULL;
const gchar *new_pad_type = NULL;
g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));
/* If our converter is already linked, we have nothing to do here */
if (gst_pad_is_linked (sink_pad)) {
g_print (" We are already linked. Ignoring.\n");
goto exit;
}
/* Check the new pad's type */
new_pad_caps = gst_pad_get_caps (new_pad);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
g_print (" It has type '%s' which is not raw audio. Ignoring.\n", new_pad_type);
goto exit;
}
/* Attempt the link */
ret = gst_pad_link (new_pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret)) {
g_print (" Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print (" Link succeeded (type '%s').\n", new_pad_type);
}
exit:
/* Unreference the new pad's caps, if we got them */
if (new_pad_caps != NULL)
gst_caps_unref (new_pad_caps);
/* Unreference the sink pad */
gst_object_unref (sink_pad);
}
Also, since you're getting a segmentation fault, i believe there is a memory issue. Are you sure you're using the CustomData structure right? I notice you're using data->element instead of data.element.