安卓如何使用libjpeg-turbo库

3
我终于成功构建了libjpeg-turbo静态库,感谢这个用于Android的libjpeg-turbo。现在我有一个由ndk-build生成的libjpeg.a和libsimd.a。
但我没有找到任何关于接下来该怎么做的信息?我正在使用内置的BitmapFactory从缓冲区(从套接字)解码JPEG到位图,这可以很好地工作。
byte[] jpgBits = new byte[jpgBitsLen];
dis.readFully(jpgBits);
Bitmap bmp = BitmapFactory.decodeByteArray(jpgBits, 0, jpgBitsLen);

如何使用libjpeg-turbo替换BitmapFactory.decodeByteArray?

我使用以下方式在我的电脑上对流进行编码:

tjhandle rmfdJpegCompressor = tjInitCompress();
tjCompress2(rmfdJpegCompressor, (unsigned char *)s_rmfdPixels, MFD_WH, 0, MFD_WH, TJPF_BGRX,
            &rmfdBits, &rmfdBitsLen, TJSAMP_420, RMFD_JPEG_QUALITY,
            0);
tjDestroy(rmfdJpegCompressor);

这个工作正常,所以我认为一定有安卓的等效物吗?

我读到了这篇文章 https://wiki.linaro.org/BenjaminGaignard/libjpeg-turboAndSkia 这是否意味着唯一使用它的方法是重建安卓源代码以便使用libjpeg-turbo?我在某个地方读到过一个兼容API和本机API的libjpeg-turbo,我很乐意使用最简单的API,因为我不想重建安卓。

我尝试了以下操作: 在我的项目根目录下创建了jni/include文件夹,并将turbojpeg.h放入其中 在我的项目根目录下创建了jni/prebuilt文件夹,并将libjpeg.a放入其中

在我的Java代码中,我放置了

private native int tjInitDecompress(); 

在MainActivity中,在onCreate方法中我添加了以下内容:

int i = tjInitDecompress();
Log.d("MainActivity", "i="+i);

该应用程序编译和运行,但在tjInitDecompress处崩溃。

日志中显示:

No implementation found for native Lcom/example.jpegtest/MainActivity;.tjInitDecompress ()I

谢谢

1个回答

5

经过一系列的努力,我终于把一些东西搞定了,现在想让有兴趣的人知道我是如何做到的。

首先,我按照这里描述的方式构建了hello-jin演示版本 https://developer.android.com/tools/sdk/ndk/index.html

然后我创建了一个新项目,复制了jni文件夹,并将c函数的名称更改为匹配新包和类名。不要在您的包名称中使用“-”和“_”,否则会出现问题,最好只使用字母和数字。

接下来,我复制了所有的libjpeg-turbo文件和文件夹到jni目录中,并测试了ndk-build是否仍然工作。

最后,我创建了一个jni封装器,用于处理libjpg函数,如下所示 tjpegini-arm.c

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
#include <jni.h>
#include "turbojpeg.h"


/*
 * Class:     libjpegturbo_jniapi
 * Method:    tjInitDecompress
 * Signature: ()I
 */
//package com.design2112.fbmslpit
//public class MainActivity
jint JNICALL Java_com_design2112_fbmslpit_MainActivity_tjInitDecompress
  (JNIEnv *env, jobject thisObj)
{
    return (int)tjInitDecompress();
}


/*
 * Class:     libjpegturbo_jniapi
 * Method:    tjDecompressHeader2
 * Signature: (I[BI)I
 */
jint JNICALL Java_com_design2112_fbmslpit_MainActivity_tjDecompressHeader2
  (JNIEnv *env, jobject thisObj, jint handle, jbyteArray jpegBuf, jint jpegSize)
{
    jbyte *real_jpegBuf = (*env)->GetByteArrayElements(env, jpegBuf, 0);
    if (!real_jpegBuf) return -1;
    //jsize length = (*env)->GetArrayLength(env, real_jpegBuf);

    /*for (i = 0; i < length; i++) {
        sum += inCArray[i];
    }*/

    int width, height, jpegSubsamp;
    int ret =  tjDecompressHeader2((tjhandle)handle,
                (unsigned char *)real_jpegBuf, (unsigned long)jpegSize, &width, &height,
                &jpegSubsamp);
    if(ret!=0) {
        return 0;
    }

    // ok, so pack width and height together
    return width<<16 | height;
}

/*
 * Class:     libjpegturbo_jniapi
 * Method:    tjDecompress2
 * Signature: (I[BI[IIIIII)V
 */
void JNICALL Java_com_design2112_fbmslpit_MainActivity_tjDecompress2
  (JNIEnv *env, jobject thisObj, jint handle, jbyteArray jpegBuf, jint jpegSize, jintArray dstBuf,
  jint width, jint pitch, jint height, jint pixelFormat, jint flags)
{
    jbyte *real_jpegBuf = (*env)->GetByteArrayElements(env, jpegBuf, 0);
    if (!real_jpegBuf) return;
    jint *real_dstBuf = (*env)->GetIntArrayElements(env, dstBuf, 0);
    if (!real_dstBuf) return;

    jsize length = (*env)->GetArrayLength(env, jpegBuf);
    tjDecompress2((tjhandle)handle,
                (unsigned char *)real_jpegBuf, (unsigned long)jpegSize, (unsigned char *)real_dstBuf,
                 width, pitch, height, pixelFormat, flags);
}

/*
 * Class:     libjpegturbo_jniapi
 * Method:    tjDestroy
 * Signature: (I)V
 */
void JNICALL Java_com_design2112_fbmslpit_MainActivity_tjDestroy
  (JNIEnv *env, jobject thisObj, jint handle)
{
    tjDestroy((tjhandle)handle);
}

重要提示:为了使此功能正常工作,您需要将com_design2112_fbmslpit_MainActivity重命名为您的包和类

将tjpegini-arm.c添加到Android.mk makefile中,然后在jni目录下运行ndk-build

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk obj/local/armeabi/libjpeg.so  LOCAL_ARM_MODE=arm

将.so 文件复制到正确的名称和位置。
cp obj/local/armeabi/libjpeg.so ../libs/armeabi/libtjpegjni-arm.so

然后在我的MainActivity.java文件中。
public class MainActivity extends Activity {


    public native int tjInitDecompress();
    public native int tjDecompressHeader2(int handle, byte[] jpegBits, int jpegBitsLen);    
    public native void tjDecompress2(int handle, byte[] jpegBits,
            int jpegBitsLen, int[] outbuffer, int width, int pitch, int height,
            int pixelFormat, int flags);
    public native void tjDestroy(int handle);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }

        File sdcard = Environment.getExternalStorageDirectory();

        //Get the text file
        File file = new File(sdcard,"/Download/test.jpg");
        int jpegBitsLen = (int) file.length();
        byte[] jpegBits = new byte[jpegBitsLen];
        DataInputStream dis;
        try {
            dis = new DataInputStream(new FileInputStream(file));
            dis.readFully(jpegBits);
            dis.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        System.loadLibrary("tjpegjni-arm");

        int jpegDec = tjInitDecompress();

        int wh = tjDecompressHeader2(jpegDec, jpegBits, jpegBitsLen);
        int width = wh>>16;
        int height = wh&0x7fff;

        int[] buffer = new int[width*height];
        tjDecompress2(jpegDec, jpegBits, jpegBitsLen, buffer, width, 0/*pitch*/, height, 2 /*TJPF_RGBX*/, 0);

        tjDestroy(jpegDec);

        Bitmap bmp = Bitmap.createBitmap(buffer, width, height, Bitmap.Config.ARGB_8888);

    }

基本上就是这样了。您可以以任何方式显示bmp。

对我来说,没有jni ndk经验,这需要我花费大量的时间去研究。如果有人发现这很有用,请给我发送一封邮件,谢谢!

更新,令人震惊的消息是,解码一个450x450的图像需要20毫秒。 内置的BitmapFactory.decodeByteArray也需要差不多同样的时间!

如果其他人尝试并得到不同的结果,请做个记录。


首先,我要为你的勇气和决心鼓掌。你确实做了一项伟大的工作。可惜的是,在开始之前你没有检查好自己的方向。在Android上,使用libjpeg-turbo无法击败内置的Jpeg解码器。特别是对于小图像而言。这个库有什么用处呢? 用于创建大型(10兆字节及以上)的Jpeg图像;用于操作大型Jpeg图像(例如旋转和裁剪)。请注意,许多设备都具有硬件支持的Jpeg编码/解码功能,这比任何NEON优化都要快得多。再次强调,这只对大型图像有意义。 - Alex Cohn
1
哦,好吧,活到老学到老,至少我现在知道了JNI和NDK。 - steveh
那么@AlexCohn,压缩也是这样吗?我正在尝试用TJCompressor替换内置的Bitmap.compress(CompressFormat.JPEG),我的图像确实非常小(它们是大约40x40的更大图像的瓦片)。我现在每秒只能压缩约10个完整帧的瓦片(对于我正在处理的图像大约256个瓦片),因此大约需要2560次小压缩,我原本期望通过使用libjpeg-turbo将其提高到约20-30 fps。如果这不可行,有没有更好的Android领域中的JPEG压缩建议? - Roberto Andrade

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接