In Android Neural Network API docs says: Creates a shared memory object from a file descriptor.
But I can't find any place that specifies how is the format of this file, on TFL source code:
allocation.cc:
MMAPAllocation::MMAPAllocation(const char* filename,
ErrorReporter* error_reporter)
: Allocation(error_reporter), mmapped_buffer_(MAP_FAILED) {
mmap_fd_ = open(filename, O_RDONLY);
if (mmap_fd_ == -1) {
error_reporter_->Report("Could not open '%s'.", filename);
return;
}
struct stat sb;
fstat(mmap_fd_, &sb);
buffer_size_bytes_ = sb.st_size;
mmapped_buffer_ =
mmap(nullptr, buffer_size_bytes_, PROT_READ, MAP_SHARED, mmap_fd_, 0);
if (mmapped_buffer_ == MAP_FAILED) {
error_reporter_->Report("Mmap of '%s' failed.", filename);
return;
}
}
nnapi_delegate.cc
NNAPIAllocation::NNAPIAllocation(const char* filename,
ErrorReporter* error_reporter)
: MMAPAllocation(filename, error_reporter) {
if (mmapped_buffer_ != MAP_FAILED)
CHECK_NN(ANeuralNetworksMemory_createFromFd(buffer_size_bytes_, PROT_READ,
mmap_fd_, 0, &handle_));
}
It means, TFL opens the file, and give this file to NNAPI. What I need is what is the format of this file that store the tensors, is it a flatbuffers file like TFL format?
Edit:
This is a sample from NNAPI doc:
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
This file training_data, how must its content be structured to NNAPI understand?
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1) - This API maps the model file in to ANeuralNetworksMemory.
The mapped address is stored in mem1 (pass by reference!)
Further, trained values that are stored in mem1 (ANeuralNetworksMemory object) is read by pointing to the appropriate offset value and copied in to the tensors of NeuralNetwork model.
ANeuralNetworksModel_setOperandValueFromMemory(model_, tensor0, mem1, offset, size);
ANeuralNetworksModel_setOperandValueFromMemory(model_, tensor1, mem1, offset+size, size);
tensor0 - pointing at offset
tensor1 - pointing at offset+size
The loading of the model file and the parsing of it are done separately. This makes it easier to mix-and-match between different memory models and different file formats. It also makes it possible to use these building blocks for other functions, like loading inputs from a file.
ANeuralNetworksMemory_createFromFd() is just used to load the model file into memory.
FlatBufferModel::BuildFromFile() gets an allocation (memory block) that represents the model. This is where ANeuralNetworksMemory_createFromFd() gets called. Then it news up a FlatBufferModel object. This calls tflite::GetModel() which is in the schema subdirectory. The schema subdirectory deserializes the flat-buffer-model from the .tflite model loaded into memory.
When NNAPIDelegate::Invoke() is called, the schema Model object is used to build the model in the Android-NN layer using calls like ANeuralNetworksModel_addOperand().
Related
I am using the following code to read files in /assets/ folder,
//AAssetManager* mgr from parameter.
AAsset* asset = AAssetManager_open(mgr, filen_ame, AASSET_MODE_BUFFER);
if (NULL == asset) {
__android_log_print(ANDROID_LOG_ERROR, "hdrijni", "_ASSET_NOT_FOUND_");
return;
}
long size = AAsset_getLength(asset);
char * buffer = (char*) malloc(sizeof(char)*size);
int byteRead = AAsset_read(asset, buffer, size);
AAsset_close(asset);
I can get the content, but sometimes the content appends some special characters.
Actually this problem not because of Asset Manager, but with the shader code I am using.
After I read the shader content, I will create the shader like:
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &buffer2, NULL);
Asset buffer or content buffer transfered by JNI might be not NULL
terminated, so you need to use 'length' parameter when calling
glShaderSource. -- From others.
Just change
glShaderSource(shader, 1, &buffer2, &length);
I have only been able to find solutions dated 2010 and earlier. So I wanted to see if there was a more up-to-date stance on this.
I'd like to avoid using Java and purely use C++, to access files (some less-or-more than 1MB) stored away in the APK. Using AssetManager means I can't access files like every other file on every other operating system (including iOS).
If not, is there a method in C++ where I could somehow map fopen/fread to the AssetManager APIs?
I actually found pretty elegant answer to the problem and blogged about it here.
The summary is:
The AAssetManager API has NDK bindings. This lets you load assets from the APK.
It is possible to combine a set of functions that know how to read/write/seek against anything and disguise them as a file pointer (FILE*).
If we create a function that takes an asset name, uses AssetManager to open it, and then disguises the result as a FILE* then we have something that's very similar to fopen.
If we define a macro named fopen we can replace all uses of that function with ours instead.
My blog has a full write up and all the code you need to implement in pure C. I use this to build lua and libogg for Android.
Short answer
No. AFAIK mapping fread/fopen in C++ to AAssetManager is not possible. And if were it would probably limit you to files in the assets folder. There is however a workaround, but it's not straightforward.
Long Answer
It IS possible to access any file anywhere in the APK using zlib and libzip in C++.
Requirements : some java, zlib and/or libzip (for ease of use, so that's what I settled for). You can get libzip here: http://www.nih.at/libzip/
libzip may need some tinkering to get it to work on android, but nothing serious.
Step 1 : retrieve APK location in Java and pass to JNI/C++
String PathToAPK;
ApplicationInfo appInfo = null;
PackageManager packMgmr = parent.getPackageManager();
try {
appInfo = packMgmr.getApplicationInfo("com.your.application", 0);
} catch (NameNotFoundException e) {
e.printStackTrace();
throw new RuntimeException("Unable to locate APK...");
}
PathToAPK = appInfo.sourceDir;
Passing PathToAPK to C++/JNI
JNIEXPORT jlong JNICALL Java_com_your_app(JNIEnv *env, jobject obj, jstring PathToAPK)
{
// convert strings
const char *apk_location = env->GetStringUTFChars(PathToAPK, 0);
// Do some assigning, data init, whatever...
// insert code here
//release strings
env->ReleaseStringUTFChars(PathToAPK, apk_location);
return 0;
}
Assuming that you now have a std::string with your APK location and you have zlib on libzip working you can do something like this:
if(apk_open == false)
{
apk_file = zip_open(apk_location.c_str(), 0, NULL);
if(apk_file == NULL)
{
LOGE("Error opening APK!");
result = ASSET_APK_NOT_FOUND_ERROR;
}else
{
apk_open = true;
result = ASSET_NO_ERROR;
}
}
And to read a file from the APK:
if(apk_file != NULL){
// file you wish to read; **any** file from the APK, you're not limited to regular assets
const char *file_name = "path/to/file.png";
int file_index;
zip_file *file;
struct zip_stat file_stat;
file_index = zip_name_locate(apk_file, file_name, 0);
if(file_index == -1)
{
zip_close(apk_file);
apk_open = false;
return;
}
file = zip_fopen_index(apk_file, file_index, 0);
if(file == NULL)
{
zip_close(apk_file);
apk_open = false;
return;
}
// get the file stats
zip_stat_init(&file_stat);
zip_stat(apk_file, file_name, 0, &file_stat);
char *buffer = new char[file_stat.size];
// read the file
int result = zip_fread(file, buffer, file_stat.size);
if(result == -1)
{
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
return;
}
// do something with the file
// code goes here
// delete the buffer, close the file and apk
delete[] buffer;
zip_fclose(file);
zip_close(apk_file);
apk_open = false;
Not exactly fopen/fread but it gets the job done. It should be pretty easy to wrap this to your own file reading function to abstract the zip layer.
I want to merge two mp3 files into one mp3 file.for example if 1st file is 1min and 2nd file is 30 sec then the output should be one min. In that one min it should play both the files.
First of all, in order to mix two audio files you need to manipulate their raw representation; since an MP3 file is compressed, you don't have a direct access to the signal's raw representation. You need to decode the compressed MP3 stream in order to "understand" the wave form of your audio signals and then you will be able to mix them.
Thus, in order to mix two compressed audio file into a single compressed audio file, the following steps are required:
decode the compressed file using a decoder to obtain the raw data (NO PUBLIC SYSTEM API available for this, you need to do it manually!).
mix the two raw uncompressed data streams (applying audio clipping if necessary). For this, you need to consider the raw data format obtained with your decoder (PCM)
encode the raw mixed data into a compressed MP3 file (as per the decoder, you need to do it manually using an encoder)
More info aboud MP3 decoders can be found here.
I am not sure if you want to do it on an Android phone (looks like that because of your tags), but if I'm right maybe try LoopStack, it's a mobile DAW (did not try it myself).
If you are just "mixing" two files without adjusting the output volume your output might clip. However I am not sure if it's possible to "mix" two mp3 files without decoding them.
If it is okay for you to merge them on your PC try Audacity, it's a free desktop DAW.
I have not done it in Android but I had done it using Adobe flex. I guess the logic remains the same. I followed the following steps:
I extracted both the mp3s into two byte arrays. (song1ByteArray, song2ByteArray)
Find out the bigger byte array. (Let's say song1ByteArray is the larger one).
Create a function which returns the mixed byte array.
private ByteArray mix2Songs(ByteArray song1ByteArray, ByteArray song2ByteArray){
int arrLength=song1ByteArray.length;
for(int i=0;i<arrLength;i+=8){ // here if you see we are incrementing the length by 8 because a sterio sound has both left and right channels 4 bytes for left +4 bytes for right.
// read left and right channel values for the first song
float source1_L=song1ByteArray.readFloat();// I'm not sure if readFloat() function exists in android but there will be an equivalant one.
float source1_R=song1ByteArray.readFloat();
float source2_L=0;
float source2_R=0;
if(song2ByteArray.bytesAvailable>0){
source2_L=song1ByteArray.readFloat();//left channel of audio song2ByteArray
source2_R=song1ByteArray.readFloat(); //right channel of audio song2ByteArray
}
returnResultArr.writeFloat((source_1_L+source_2_L)/2); // average value of the source 1 and 2 left channel
returnResultArr.writeFloat((source_1_R+source_2_R)/2); // average value of the source 1 and 2 right channel
}
return returnResultArr;
}
1. Post on Audio mixing in Android
2. Another post on mixing audio in Android
3. You could leverage Java Sound to mix two audio files
Example:
// First convert audiofile to audioinputstream
audioInputStream = AudioSystem.getAudioInputStream(soundFile);
audioInputStream2 = AudioSystem.getAudioInputStream(soundFile2);
// Create one collection list object using arraylist then add all AudioInputStreams
Collection list=new ArrayList();
list.add(audioInputStream2);
list.add(audioInputStream);
// Then pass the audioformat and collection list to MixingAudioInputStream constructor
MixingAudioInputStream mixer=new MixingAudioInputStream(audioFormat, list);
// Finally read data from mixed AudionInputStream and give it to SourceDataLine
nBytesRead =mixer.read(abData, 0,abData.length);
int nBytesWritten = line.write(abData, 0, nBytesRead);
4. Try AudioConcat that has a -m option for mixing
java AudioConcat [ -D ] [ -c ] | [ -m ] | [ -f ] -o outputfile inputfile ...
Parameters.
-c
selects concatenation mode
-m
selects mixing mode
-f
selects float mixing mode
-o outputfile
The filename of the output file
inputfile
the name(s) of input file(s)
5. You could use ffmpeg android wrapper using a syntax and approach as explained here
This guy used the JLayer library in a project quite similar to yours. He also gives you a guide on how to integrate that library in your android application directly recompiling the jar.
Paraphrasing his code it is very easy to accomplish your task:
public static byte[] decode(String path, int startMs, int maxMs)
throws IOException, com.mindtherobot.libs.mpg.DecoderException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);
float totalMs = 0;
boolean seeking = true;
File file = new File(path);
InputStream inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
try {
Bitstream bitstream = new Bitstream(inputStream);
Decoder decoder = new Decoder();
boolean done = false;
while (! done) {
Header frameHeader = bitstream.readFrame();
if (frameHeader == null) {
done = true;
} else {
totalMs += frameHeader.ms_per_frame();
if (totalMs >= startMs) {
seeking = false;
}
if (! seeking) {
SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);
if (output.getSampleFrequency() != 44100
|| output.getChannelCount() != 2) {
throw new com.mindtherobot.libs.mpg.DecoderException("mono or non-44100 MP3 not supported");
}
short[] pcm = output.getBuffer();
for (short s : pcm) {
outStream.write(s & 0xff);
outStream.write((s >> 8 ) & 0xff);
}
}
if (totalMs >= (startMs + maxMs)) {
done = true;
}
}
bitstream.closeFrame();
}
return outStream.toByteArray();
} catch (BitstreamException e) {
throw new IOException("Bitstream error: " + e);
} catch (DecoderException e) {
Log.w(TAG, "Decoder error", e);
throw new com.mindtherobot.libs.mpg.DecoderException(e);
} finally {
IOUtils.safeClose(inputStream);
}
}
public static byte[] mix(String path1, String path2) {
byte[] pcm1 = decode(path1, 0, 60000);
byte[] pcm2 = decode(path2, 0, 60000);
int len1=pcm1.length;
int len2=pcm2.length;
byte[] pcmL;
byte[] pcmS;
int lenL; // length of the longest
int lenS; // length of the shortest
if (len2>len1) {
lenL = len1;
pcmL = pcm1;
lenS = len2;
pcmS = pcm2;
} else {
lenL = len2;
pcmL = pcm2;
lenS = len1;
pcmS = pcm1;
}
for (int idx = 0; idx < lenL; idx++) {
int sample;
if (idx >= lenS) {
sample = pcmL[idx];
} else {
sample = pcmL[idx] + pcmS[idx];
}
sample=(int)(sample*.71);
if (sample>127) sample=127;
if (sample<-128) sample=-128;
pcmL[idx] = (byte) sample;
}
return pcmL;
}
Note that I added attenuation and clipping in the last rows: you always have to do both when mixing two waveforms.
If you don't have memory/time requirements you can make an int[] of the sum of the samples and evaluate what is the best attenuation to avoid clipping.
To merge (overlap) two sound files, you can use This FFMPEG library.
Here is the Documentation
In their sample you can just enter the command you want. So lets talk about the command that we need.
-i [FISRST_FILE_PATH] -i [SECOND_FILE_PATH] -filter_complex amerge -ac 2 -c:a libmp3lame -q:a 4 [OUTPUT_FILE_PATH]
For first and second file paths, you will get the absolute path of the sound file.
1- If it is on storage then it is a sub folder for Environment.getExternalStorageDirectory().getAbsolutePath()
2- If it is assets so it should be a sub folder for file:///android_asset/
For the output path, Make sure to add the extension
ex.
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/File Name.mp3"
I didn't get any fine solution.but we can do some trick here.. :)
You can assign both mp3 files to two different MediaPlayer object.then play both files at a time with a button.compare both mp3 files to find the longest duration.after that Use a AudioReorder to record to that duration. it will solve your problem..I know its not a right way but hope it will help you.. :)
I'm trying to load a TTF file directly from a ZIP archive, using libzip and FreeType.
In particular, I'm using the FT_Open_Face function which can read from custom read/close functions (ft_zip_read and ft_zip_close). But although the file is apparently fully read, FT_Open_Face returns FT_Err_Unknown_File_Format. Opening the same file directly from the disk works fine.
I really don't know how to debug this, can anybody help?
The only thing I can imagine to be the issue right now is that my ft_zip_read function does not support seeking, the documentation says:
This function might be called to perform a seek or skip operation with
a ‘count’ of 0. A non-zero return value then indicates an error.
And it is indeed called with count 0 a couple of times, but I can't see any way to do a seek with libzip.
unsigned long ft_zip_read(FT_Stream stream, unsigned long offset,
unsigned char* buffer, unsigned long count)
{
zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
return zip_fread(file, buffer + offset, count);
}
void ft_zip_close(FT_Stream stream)
{
zip_file* file = static_cast<zip_file*>(stream->descriptor.pointer);
zip_fclose(file);
}
FT_Face load_zipped_face(const std::string& name, unsigned int size,
const std::string& zip_path)
{
FT_Library library;
FT_Error error = FT_Init_FreeType(&library);
if (error)
throw freetype_error_string("Failed to initialise FreeType", error);
int zip_error;
zip* zip = zip_open(zip_path.c_str(), 0, &zip_error);
if (!zip) {
std::ostringstream message_stream;
message_stream << "Error loading ZIP (" << zip_path << "): "
<< zip_error;
throw message_stream.str();
}
std::string face_path = name + ".ttf";
struct zip_stat stat;
if (zip_stat(zip, face_path.c_str(), 0, &stat))
throw std::string("zip_stat failed");
zip_file* file = zip_fopen(zip, face_path.c_str(), 0);
if (file == 0)
throw face_path + ": " + strerror(errno);
FT_StreamDesc descriptor;
descriptor.pointer = file;
FT_StreamRec* stream = new FT_StreamRec;
stream->base = 0;
stream->size = stat.size;
stream->descriptor = descriptor;
stream->read = &ft_zip_read;
stream->close = &ft_zip_close;
FT_Open_Args open_args;
open_args.flags = FT_OPEN_STREAM;
open_args.stream = stream;
FT_Face face;
error = FT_Open_Face(library, &open_args, 0, &face);
zip_close(zip);
if (error == FT_Err_Unknown_File_Format)
throw std::string("Unsupported format");
else if (error)
throw freetype_error_string("Unknown error loading font", error);
error = FT_Set_Pixel_Sizes(face, 0, size);
if (error)
throw freetype_error_string("Unable to set pixel sizes", error);
return face;
}
Seeking the truth
To be able to seek in a compressed datastream you need to decompress the stream up until the point you wish to seek to (some exceptions exist like streams that have reset markers and indexes only have to decompress since the previous marker). This is just plain inefficient if done often (not to mention that you need to write code for it yourself).
Now thinking about it, the only reason you'd want to not load the entire face into memory and have a custom IO for a font file is if it is too big to keep in memory; so that makes seeking mandatory for the stream IO interface of FT.
What can you do?
If the file is small enough: Read it all into memory and use FT_New_Memory_Face to load the face from memory.
If the file is too big so that you don't want the entire face in memory at once, extract the font file to a temporary file and read from that. (Use windows/unix/cstdio temp file API to have a well behaved temporary file)
If neither of the above suits you then you can implement your own caching and seekable zip stream on top of libzip and pass that to FT. This is probably cumbersome and involves some work so I'd go with one of the other two personally :)
I am trying to load a TGA file in Android NDK.
I open the file using AssetManager, read in the entire contents of the TGA file into a memory buffer, and then I try to extract the pixel data from it.
I can read the TGA header part of the file without any problems, but when I try to advance the memory pointer past the TGA header, the app crashes. If I don't try to advance the memory pointer, it does not crash.
Is there some sort of limitation in Android NDK for pointer arithmetic?
Here is the code:
This function opens the asset file:
char* GEAndroid::OpenAssetFile( const char* pFileName )
{
char* pBuffer = NULL;
AAssetManager* assetManager = m_pState->activity->assetManager;
AAsset* assetFile = AAssetManager_open(assetManager, pFileName, AASSET_MODE_UNKNOWN);
if (!assetFile) {
// Log error as 'error in opening the input file from apk'
LOGD( "Error opening file %s", pFileName );
}
else
{
LOGD( "File opened successfully %s", pFileName );
const void* pData = AAsset_getBuffer(assetFile);
off_t fileLength = AAsset_getLength(assetFile);
LOGD("fileLength=%d", fileLength);
pBuffer = new char[fileLength];
memcpy( pBuffer, pData, fileLength * sizeof( char ) );
}
return pBuffer;
}
And down here in my texture class I try to load it:
char* pBuffer = g_pGEAndroid->OpenAssetFile( fileNameWithPath );
TGA_HEADER textureHeader;
char *pImageData = NULL;
unsigned int bytesPerPixel = 4;
textureHeader = *reinterpret_cast<TGA_HEADER*>(pBuffer);
// I double check that the textureHeader is valid and it is.
bytesPerPixel = textureHeader.bits/8; // Divide By 8 To Get The Bytes Per Pixel
m_imageSize = textureHeader.width*textureHeader.height*bytesPerPixel; // Calculate The Memory Required For The TGA Data
pImageData = new char[m_imageSize];
// the line below causes the crash
pImageData = reinterpret_cast<char*>(pBuffer + sizeof( TGA_HEADER)); // <-- causes a crash
If I replace the line above with the following line (even though it is incorrect), the app runs, although obviously the texture is messed up.
pImageData = reinterpret_cast<char*>(pBuffer); // <-- does not crash, but obviously texture is messed up.
Anyone have any ideas?
Thanks.
Why reinterpret_cast? You're adding an integer to a char*; that operation produces a char*. No typecast necessary.
One caveat for pointer juggling on Android (and on ARM devices in general): ARM cannot read/write unaligned data from memory. If you read/write an int-sized variable, it needs to be at an address that's a multiple of 4; for short, a multiple of 2. Bytes can be at any address. This does not, as far as I can see, apply to the presented snippet. But do keep in mind. It does throw off binary format parsing occasionally, especially when ported from Intel PCs.
Simply assigning an unaligned value to a pointer does not crash. Dereferencing it might.
Sigh, I just realized the mistake. I allocate memory for pImageData, then set the point to the buffer. This does not sit well when I try to create an OpenGL texture with the pixel data. Modifying it so I memcpy the pixel data from (pBuffer + sizeof( TGA_HEADER) ) to pImageData fixes the problem.