Android音视频开发实战02-Jni

这篇具有很好参考价值的文章主要介绍了Android音视频开发实战02-Jni。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 JNI

1.1 什么是JNI

JNI是Java Native Interface的缩写,是Java提供的一种机制,用于在Java代码中调用本地(C/C++)代码。它允许Java代码与本地代码进行交互,通过JNI,Java应用程序可以调用一些原生库或者操作系统API,以获取更好的性能和更强的功能支持。

使用JNI需要编写一些Native方法,并将其实现在本地代码(如C/C++)中。这些本地方法可以直接从Java代码中调用,从而获得更高的性能和更灵活的控制权。通常情况下,为了方便和可维护性,我们会将所有本地方法的实现都封装到一个动态链接库文件中,然后在Java代码中加载并调用其中的函数。

JNI为Java程序员提供了一种无缝集成本地代码的方式,使得Java应用程序可以更加高效地利用系统资源,从而提升性能和扩展性。

1.2 JNI的优劣势

JNI的优势:

  1. 快速的执行效率:通过JNI,可以将Java应用程序与本地代码结合起来,从而获得更高的执行效率。因为本地代码是通过C/C++等编程语言编写的,这些语言通常比Java更接近硬件和操作系统,因此本地代码在执行时可以更加快速和高效。

  2. 可以访问底层系统资源:通过JNI,Java应用程序可以直接调用操作系统的API,从而获得对系统底层资源(如文件、网络、内存等)的直接访问能力。这使得Java程序员可以实现更加灵活和复杂的功能,同时也可以提高程序的运行效率。

  3. 跨平台性:虽然本地代码必须针对每个平台进行编译,但是一旦编译完成,它们就可以在任何支持JNI的平台上运行。这意味着开发人员可以使用最适合自己的工具和环境来编写本地代码,而不需要考虑跨平台问题。

JNI的劣势:

  1. 复杂度较高:JNI需要开发人员熟悉Java和本地代码两种编程语言,并且需要掌握JNI规范及其相关的工具和技术。这使得JNI的学习曲线较陡峭,初学者可能需要花费较长时间才能掌握。

  2. 可移植性差:尽管JNI可以跨平台运行,但是在不同的平台上,本地代码必须重新编译和链接,这可能会带来一些兼容性问题。此外,由于Java虚拟机(JVM)的不同实现可能存在差异,因此在某些情况下,JNI代码也可能无法在不同的JVM上正确运行。

  3. 方法调用成本高,相对于Java调用Java代码,Java调用Jni的性能开销会更大一些,因此只有在执行特别耗时的代码的时候才会使用Jni,用C++代码的执行效率来覆盖掉方法调用的性能开销
    4.数据类型不通用,C/C++的数据类型与Java并不通用,在相互调用的时候需要做转化
    5.C/C++调用Java代码很复杂,很不方便

1.3 JNI常见的数据类型

JNI支持的数据类型可以分为两类:基本类型和引用类型。

基本类型包括:

  • jboolean:布尔类型,取值范围是true或false。
  • jbyte:字节类型,取值范围是-128到127。
  • jchar:字符类型,取值范围是0到65535。(Java中的char类型被映射为C语言的unsigned short类型)
  • jshort:短整型,取值范围是-32768到32767。
  • jint:整型,取值范围与平台相关,通常是32位有符号整数。
  • jlong:长整型,取值范围与平台相关,通常是64位有符号整数。
  • jfloat:单精度浮点型,取值范围约为1.4E-45到3.4E+38。
  • jdouble:双精度浮点型,取值范围约为4.9E-324到1.8E+308。

引用类型包括:

  • jobject:表示一个Java对象的引用。
  • jclass:表示一个Java类的引用。
  • jstring:表示一个Java字符串的引用。
  • jarray:表示一个Java数组的引用。
  • jthrowable:表示一个Java异常的引用。

基本数据类型是不需要手动释放,但是和Java不一样的是引用类型是需要手动调用释放的

二 JNI常见的数据处理方式

2.1 基本数据处理方式

基本数据在Java Jni C/C++之间差别不大,详见下面的对应关系:

Java JNI C/C++ 占用内存大小 备注
boolean jboolean uint8_t unsigned 8 bits
byte jbyte int8_t signed 8 bits
char jchar uint16_t unsigned 16 bits
short jshort int16_t signed 16 bits
int jint int32_t signed 32 bits
long jlong int64_t signed 64 bits 在Native是用int64_t去承接long,不要用Native自身的long类型去承接
float jfloat float 32-bit IEEE 754 IEEE 754是一种标准,由IEEE制定
double jdouble double 64-bit IEEE 754

2.2 ​String相互传递

2.2.1获取Java的Sting

extern "C"
JNIEXPORT void JNICALL
Java_com_luoye_bzmedia_BZMedia_testString(JNIEnv *env, jclass clazz, jstring video_path_) {
    const char *video_path = env->GetStringUTFChars(video_path_, JNI_FALSE);
    ...
	//必须要释放
    env->ReleaseStringUTFChars(video_path_, video_path);
}

2.2.2 C/C++的数据转换为String

extern "C"
JNIEXPORT jstring JNICALL
Java_com_luoye_bzmedia_BZMedia_getString(JNIEnv *env, jclass clazz) {
    const char *imagePath="hello world!";
    return env->NewStringUTF(imagePath);
}

2.3 ​数组相互传递

2.3.1获取数组

extern "C" JNIEXPORT jint JNICALL
Java_com_luoye_bzmedia_BZMedia_mergeVideoOrAudio(JNIEnv *env, jclass type,
        jobjectArray inputPaths,
        jstring outPutPath) {

    size_t arrayLength = static_cast<size_t>(env->GetArrayLength(inputPaths));
    char **pstr = (char **) malloc(arrayLength * sizeof(char *));
    memset(pstr, 0, arrayLength * sizeof(char *));

    for (int i = 0; i < arrayLength; i++) {
        jstring javaPath = (jstring) env->GetObjectArrayElement(inputPaths, i);
        const char *path = env->GetStringUTFChars(javaPath, JNI_FALSE);
        size_t length = strlen(path) + 1;
        char *buffer = static_cast<char *>(malloc(length));
        memset(buffer, 0, length);
        sprintf(buffer, "%s", path);
        env->ReleaseStringUTFChars(javaPath, path);

        pstr[i] = buffer;
    }

    for (int i = 0; i < arrayLength; i++) {
        free(pstr[i]);
    }
    free(pstr);
    
    return 0;
}

2.3.2返回数组

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_luoye_bzmedia_BZMedia_getVideoSize(JNIEnv *env, jclass clazz, jstring video_path_) {

    if (nullptr == video_path_) {
        BZLogUtil::logE("getVideoSize nullptr == video_path_");
        return nullptr;
    }
    const char *inputPath = env->GetStringUTFChars(video_path_, 0);
    int *ret = VideoUtil::getVideoSize(inputPath);
    env->ReleaseStringUTFChars(video_path_, inputPath);
    if (nullptr == ret) {
        return nullptr;
    }
    jintArray size = env->NewIntArray(2);
    env->SetIntArrayRegion(size, 0, 2, ret);
    delete[]ret;
    return size;
}

​2.4Java对象相互传递

Java对象相互传递需要特别注意的是,字段和方法签名,Java中的字段和方法签名包含了相应成员的名称、类型和参数列表等信息,可以唯一地标识一个成员,获取方式如下:
javap -s class_name
由于是正对class的所以我们需要在build目录下执行这个命令而不是Java源文件,样例如下:

public class com.luoye.bzmedia.bean.VideoInfo {
  public int width;
    descriptor: I
  public com.luoye.bzmedia.bean.VideoInfo();
    descriptor: ()V
		
  public int getHeight();
    descriptor: ()I

  public void setHeight(int);
    descriptor: (I)V

  public long getDuration();
    descriptor: ()J

  public void setDuration(long);
    descriptor: (J)V
		
}

只有public类型的字段和方法才能获取到签名

2.4.1获取Java对象

extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_startRecord(JNIEnv *env, jclass clazz,
                                           jobject videoRecordParamsObj) {
    VideoRecordParams videoRecordParams;
    jclass videoRecordParamsClass = env->GetObjectClass(videoRecordParamsObj);
    jint srcWidth = env->GetIntField(videoRecordParamsObj,
                                     env->GetFieldID(videoRecordParamsClass, "inputWidth", "I"));
    videoRecordParams.inputWidth = srcWidth;
}

2.4.2返回Java对象

extern "C"
JNIEXPORT jobject JNICALL
Java_com_luoye_bzmedia_BZMedia_getVideoInfo(JNIEnv *env, jclass clazz, jstring video_path_) {

    if (nullptr == video_path_) {
        BZLogUtil::logE("getVideoSize nullptr == video_path_");
        return nullptr;
    }
    const char *inputPath = env->GetStringUTFChars(video_path_, 0);
    VideoInfo *pInfo = VideoUtil::getVideoInfo(inputPath);
    env->ReleaseStringUTFChars(video_path_, inputPath);
    if (nullptr == pInfo) {
        return nullptr;
    }
    jclass videoInfoClass = env->FindClass("com/luoye/bzmedia/bean/VideoInfo");
    jmethodID constructorMid = env->GetMethodID(videoInfoClass, "<init>", "()V");
    jobject videoInfoObj = env->NewObject(videoInfoClass, constructorMid);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setWidth", "(I)V"), pInfo->width);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setHeight", "(I)V"), pInfo->height);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setDuration", "(J)V"), pInfo->duration);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setRotate", "(I)V"), pInfo->rotate);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setFrameRate", "(D)V"), pInfo->frameRate);
    env->CallVoidMethod(videoInfoObj, env->GetMethodID(videoInfoClass, "setBitRate", "(J)V"), pInfo->bitRate);
    delete pInfo;
    return videoInfoObj;
}

2.5 ​Bitmap的数据相传递

2.5.1获取Bitmap的数据

extern "C" JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_addVideoData4Bitmap(JNIEnv *env, jclass type,
                                                   jlong nativeHandle, jobject bitmap,
                                                   jint width, jint height) {
    int ret = 0;
    void *pixelscolor = NULL;

    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixelscolor)) < 0) {
        BZLogUtil::logE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return ret;
    }
    frame_RGBA->data[0] = (uint8_t *) pixelscolor;
    AVFrame *videoFrame = VideoUtil::allocVideoFrame(AV_PIX_FMT_YUV420P, width, height);
    sws_scale(sws_video_to_YUV,
              (const uint8_t *const *) frame_RGBA->data,
              frame_RGBA->linesize, 0, videoFrame->height,
              videoFrame->data,
              videoFrame->linesize);

    AndroidBitmap_unlockPixels(env, bitmap);
    return addVideoData(nativeHandle, videoFrame);
}


2.5.2返回Bitmap的数据

extern "C" JNIEXPORT jobject JNICALL
Java_com_luoye_bzmedia_BZMedia_bzReadPixelsNative(JNIEnv *env, jclass type,
                                                  jint startX,
                                                  jint startY, jint width,
                                                  jint height) {
    if (width < 1 || height < 1) {
        BZLogUtil::logE("params is error width<1||height<1");
        return nullptr;
    }

    JNIEnv *jniEnv = nullptr;
    bool needDetach = JvmManager::getJNIEnv(&jniEnv);
    int ret;
    void *targetPixels;
    jclass bitmapCls = jniEnv->FindClass("android/graphics/Bitmap");

    jobject newBitmap;
    jmethodID createBitmapFunctionMethodID = jniEnv->GetStaticMethodID(bitmapCls, "createBitmap",
                                                                       "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
    jstring configName = jniEnv->NewStringUTF("ARGB_8888");
    jclass bitmapConfigClass = jniEnv->FindClass("android/graphics/Bitmap$Config");

    jmethodID valueOfBitmapConfigFunctionMethodID = jniEnv->GetStaticMethodID(bitmapConfigClass,
                                                                              "valueOf",
                                                                              "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");
    jobject bitmapConfigObj = jniEnv->CallStaticObjectMethod(bitmapConfigClass,
                                                             valueOfBitmapConfigFunctionMethodID,
                                                             configName);

    newBitmap = jniEnv->CallStaticObjectMethod(bitmapCls, createBitmapFunctionMethodID, width,
                                               height, bitmapConfigObj);

    if ((ret = AndroidBitmap_lockPixels(jniEnv, newBitmap, &targetPixels)) < 0) {
        BZLogUtil::logE("gifDataCallBack AndroidBitmap_lockPixels() targetPixels failed ! error=%d",
                        ret);
    }
    if (ret >= 0) {
        char *data = new char[width * height * 4];
        glReadPixels(startX, startY, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
        memcpy(targetPixels, data, (size_t) (width * height * 4));
        AndroidBitmap_unlockPixels(jniEnv, newBitmap);
        delete[](data);
    }

    jniEnv->DeleteLocalRef(bitmapCls);
    jniEnv->DeleteLocalRef(configName);
    jniEnv->DeleteLocalRef(bitmapConfigObj);
    jniEnv->DeleteLocalRef(bitmapConfigClass);

    if (needDetach)JvmManager::getJavaVM()->DetachCurrentThread();

    return newBitmap;
}

2.6 Java与C/C++类的对象相互持有

如果是纯Java对象或者纯C/C++要想持有另外一个对象是一件很容易的事情,但是要想在Java与C/C++类的对象中相互持有彼此的实例,就显得很无助
这里就涉及到内存存储对象的机制了,内存一般分为栈内存与堆内存,栈内存存储基本数据类型与变量名,堆内存存储实际的对象,这个对象有一个唯一的地址,通过内存地址可以访问它,在Java中叫引用,在C/C++中叫指针,那么问题就转换到在Java中怎么存储C++对象的指针,在C++中怎么存储Java对象的引用
我们知道机器一般分为32位机器和64位机器,这个本质上说的是内存寻址能力的范围,32位机器的寻址能力为2的32次方,64位机器的寻址能力为2的64次方,也就是说64位机器最大有2的64次方个内存地址,我们只需要用一个变量来记录这个内存地址就好了,无疑用long/int64_t 类型是最合适的,他们有8字节,64bit的大小,刚好可以存下这些内存地址
Android音视频开发实战02-Jni

2.6.1 Java 存储C/C++对象

//返回C/C++对象,强转为jlong
extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_startRecord(JNIEnv *env, jclass clazz,
                                           jobject videoRecordParamsObj) {
    VideoRecorder *videoRecorder = new VideoRecorder();
    return reinterpret_cast<int64_t>(videoRecorder);
	}
//Java调用方式
	long nativeHandle = BZMedia.startRecord(videoRecordParams);
//传入C/C++对象的地址
	long ret = BZMedia.addYUV420Data(nativeHandle, buffer, pts);
//在C/C++代码中获取内存地址,并转换为C/C++对象,内存地址=0的时候是NULL,内存地址也可能为负值,这个需要注意一下
	extern "C"
JNIEXPORT jlong JNICALL
Java_com_luoye_bzmedia_BZMedia_addYUV420Data(JNIEnv *env, jclass clazz, jlong native_handle,
                                             jbyteArray data_, jlong pts) {
    if (native_handle == 0) {
        return -1;
    }
    VideoRecorder *videoRecorder = reinterpret_cast<VideoRecorder *>(native_handle);
    unsigned char *buffer = (unsigned char *) env->GetByteArrayElements(data_, nullptr);
    long ret = videoRecorder->addVideoData(buffer, pts);
    env->ReleaseByteArrayElements(data_, reinterpret_cast<jbyte *>(buffer), 0);
    return ret;
}
	

2.6.3 C/C++ 存储Java对象

同上,Java对象的引用也可以转换为一个int64_t的内存地址,因此也可以很方便的在C/C++对象中存储,在需要调用Java方法的时候就可以很方便

三 回调方法的写法

结合2.6的对象相互存储的技术就可以实现C++回调Java方法,至于Java回调C++同理,由于这种场景比较少我们以C++回调Java方法为例
大致步骤如下:

  1. 在Java层定义回调接口
  2. Java调用Native方法并传入接口对象
  3. Native获取Java接口对象并存储到对象中
  4. 在C++对象中通过函数指针调用C函数,并传入Java接口对象的地址
  5. C函数使用JNI调用Java接口函数

给一个完整的Demo:
Java 方法:

    {
        testCallBack(new OnActionListener() {
            @Override
            public void progress(float progress) {
                Log.d(TAG, "native call back progress=" + progress);
            }
        });
    }

    public native void testCallBack(OnActionListener onActionListener);

    public interface OnActionListener {
        void progress(float progress);
    }

Native方法:

void callBackGateway(int64_t methodHandle, float progress) {
    JNIEnv *jniEnv = nullptr;
    bool needDetach = JvmManager::getJNIEnv(&jniEnv);
    if (methodHandle == 0 || nullptr == jniEnv) {
        jniEnv = nullptr;
        if (needDetach)
            JvmManager::getJavaVM()->DetachCurrentThread();
        return;
    }
    auto *videoPlayerMethodInfo = (ActionMethodInfo *) methodHandle;

    jniEnv->CallVoidMethod(videoPlayerMethodInfo->listenerObj,
                           videoPlayerMethodInfo->onProgressMethodId, progress);
    jniEnv = nullptr;
    if (needDetach)
        JvmManager::getJavaVM()->DetachCurrentThread();
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_ffmpegtest_MainActivity_testCallBack(JNIEnv *env, jobject thiz, jobject on_action_listener) {
    CallBackTest callBackTest;

    auto *videoPlayerMethodInfo = new ActionMethodInfo();
    //CallBack一般涉及到多线程交互,一般通过NewGlobalRef作为全局变量
    jobject listenerObj = env->NewGlobalRef(on_action_listener);
    videoPlayerMethodInfo->listenerObj = listenerObj;
    jclass listenerClass = env->GetObjectClass(on_action_listener);
    videoPlayerMethodInfo->onProgressMethodId = env->GetMethodID(listenerClass,
                                                                        "progress",
                                                                        "(F)V");
    env->DeleteLocalRef(listenerClass);
    callBackTest.init(reinterpret_cast<int64_t>(videoPlayerMethodInfo), callBackGateway);
    env->DeleteGlobalRef(listenerObj);
    delete videoPlayerMethodInfo;
}

//CallBackTest.h
class CallBackTest {
public:
    int init(int64_t handle, void (*progressCallBack)(int64_t javaHandle, float progress));
};

//CallBackTest.cpp
int CallBackTest::init(int64_t handle, void (*progressCallBack)(int64_t, float)) {
    progressCallBack(handle, 0);
    progressCallBack(handle, 0.2);
    progressCallBack(handle, 0.4);
    progressCallBack(handle, 0.6);
    progressCallBack(handle, 0.8);
    progressCallBack(handle, 1);
    return 0;
}

//JvmManager.h
class JvmManager {
public:
    static JavaVM *getJavaVM();

    static bool getJNIEnv(JNIEnv **pJNIEnv);

    static int32_t JNI_VERSION;
};

//JvmManager.cpp
#include <android/log.h>
#include "JvmManager.h"

extern "C" {
#include <libavcodec/ffmpeg_jni.h>
}
JavaVM *bzJavaVM = nullptr;

int32_t JvmManager::JNI_VERSION = JNI_VERSION_1_6;

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    bzJavaVM = vm;
    __android_log_print(ANDROID_LOG_DEBUG, "bz_", "JNI_OnLoad success");
    av_jni_set_java_vm(vm, NULL);
    if (vm->GetEnv(reinterpret_cast<void **>(&vm), JNI_VERSION_1_6) != JNI_OK) {
        JvmManager::JNI_VERSION = JNI_VERSION_1_4;
        return JNI_VERSION_1_4;
    }
    return JNI_VERSION_1_6;
}

bool JvmManager::getJNIEnv(JNIEnv **pJNIEnv) {
    if (NULL == bzJavaVM) {
        return false;
    }
    bzJavaVM->GetEnv((void **) pJNIEnv, JNI_VERSION);
    if (NULL != *pJNIEnv) {
        return false;
    } else {
        bzJavaVM->AttachCurrentThread(pJNIEnv, NULL);
        return true;
    }
}

JavaVM *JvmManager::getJavaVM() {
    return bzJavaVM;
}

四 Jni资源回收

C++,Java的对象都有自身的新建与回收的机制与方法,Jni也不例外主要是涉及到引用的释放,只要父类是jobject的都涉及到手动释放,主要的释放方法为:

  1. DeleteLocalRef
  2. DeleteGlobalRef
  3. DeleteWeakGlobalRef
  4. ReleaseStringUTFChars
  5. ReleaseStringChars
  6. ReleasePrimitiveArrayCritical
  7. ReleaseIntArrayElements
  8. GetDirectBufferAddress
  9. FreeDirectByteBuffer

常见新建与回收的情况如下:

jstring:

env->GetStringUTFChars
env->ReleaseStringUTFChars
如:
const char *mediaPath = env->GetStringUTFChars(media_path, 0);
env->ReleaseStringUTFChars(media_path, mediaPath);

jclass

env->GetObjectClass
env->DeleteLocalRef
如:
jclass listenerClass = env->GetObjectClass(listener);
env->DeleteLocalRef(listenerClass);

jobject

env->NewGlobalRef
env->DeleteGlobalRef
如:
jobject actionObj = env->NewGlobalRef(obj);
env->DeleteLocalRef(actionClass);

数组

env->GetIntArrayElements
env->ReleaseIntArrayElements
如:
jint *elems = env->GetIntArrayElements(arr, 0);
env->ReleaseIntArrayElements(arr, elems, 0);

五 如何防止SO被脱裤

在Android侧SO的调用是通过JNI调入的,也就是说我们只要知道Java的native方法就可以直接使用SO的能力,再通过二进制修改掉native方法路径,以及SO的名字就可以完完全全把SO的能力直接复用,而且基本发现不了,因此我们需要对SO进行处理,也就是需要做鉴权处理。
SO需要做鉴权就需要拿到比较唯一的身份识别,在Android侧能做为身份识别的有Android包名以及APK签名,一般做鉴权都是基于这两者来做的,但是系统方法是可以被hook掉的,一般来说我们无法通过获取包名,以及签名来做鉴权。
我这里提供的一种方案是通过加密包名,提前放到SO里面,然后遍历包名下的私有目录,通过判断是否有文件权限的方式来鉴权,这样可以不调Java获取包名以及签名的方法就可以做好鉴权
相关Demo详见:https://github.com/bookzhan/bzmedia/tree/master/bzmedialib/src/main/cpp/permission 相关的代码文章来源地址https://www.toymoban.com/news/detail-500838.html

到了这里,关于Android音视频开发实战02-Jni的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 音视频开发:音频编码原理+采集+编码实战

    消除冗余信息,压缩量最大,也叫有损压缩 剔除人耳听觉范围外的音频信号20Hz以下和20000Hz以上; 去除被掩蔽的音频信号,信号的遮蔽可以分为频域遮蔽和时域遮蔽; 频域遮蔽效应 屏蔽70分贝以下,20HZ以下,20000HZ以上 屏蔽分贝小,频率小的声音 两个频率相近发出的声音,

    2024年02月05日
    浏览(45)
  • Android 音视频开发—MediaPlayer音频与视频的播放介绍

    Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码和播放音视频。 它支持三种不同的媒体来源: 本地资源 内部的URI,比如你可以通过ContentResolver来获取 外部URL(流

    2024年02月10日
    浏览(45)
  • 【原理+实战+视频+源码】抖音,快手大热背后——Android 贴心的音视频学习指南来咯

    (三)C 与 C++之预处理命令与用 typedef 命名已有类型 JNI 模块 JNI 开发之 静态注册与动态注册(一) JNI 开发之方法签名与 Java 通信(二) JNI 开发之局部引用、全局引用和弱全局引用(三) 二、中级进阶篇 学习 Android 平台 OpenGL ES API,了解 OpenGL 开发的基本流程,使用 OpenG

    2024年04月22日
    浏览(56)
  • 【原理+实战+视频+源码】抖音,快手大热背后——Android-贴心的音视频学习指南来咯

    下面将这份文档的内容以图片的形式展现出来,但篇幅有限只能展示部分,如果你需要“高清完整的pdf版”,可以直接点击我的【GitHub】即可免费领取。 一、初级入门篇 初级入门篇主要是接触Android多媒体展示相关的API, 通过单独的列举和使用这些API,对Android音视频处理有

    2024年04月17日
    浏览(34)
  • 音视频开发实战03-FFmpeg命令行工具移植

    作为一个音视频开发者,在日常工作中经常会使用ffmpeg 命令来做很多事比如转码 ffmpeg -y -i test.mov -g 150 -s 1280x720 -codec libx265 -r 25 test_h265.mp4 ,水平翻转视频: ffmpeg -i src.mp4 -vf hflip -acodec copy -vcodec h264 -b 22000000 out.mp4 ,视频截取: ffmpeg -i input.wmv -ss 00:00:30.0 -c copy -t 00:00:10.0 ou

    2024年02月16日
    浏览(48)
  • 【秒懂音视频开发】15_AAC编码实战

    本文将分别通过命令行、编程2种方式进行AAC编码实战,使用的编码库是libfdk_aac。 fdk-aac对输入的PCM数据是有参数要求的,如果参数不对,就会出现以下错误: 采样格式 必须是16位整数PCM。 采样率 支持的采样率有(Hz): 8000、11025、12000、16000、22050、24000、32000 44100、48000、

    2024年02月03日
    浏览(71)
  • 音视频开发系列(6)——全面了解Android MediaFormat

    MediaFormat 是 Android 平台中用于描述音视频格式的类,它提供了许多 API 用于设置和获取音视频的格式信息。以下是 MediaFormat 类的主要 API: 用于创建音频和视频格式的 MediaFormat 对象。需要指定媒体类型(例如 audio/mp4a-latm 或 video/avc)、媒体的采样率、通道数、码率、帧率等信

    2024年02月01日
    浏览(29)
  • Android开源计划-一周开发app,webrtc音视频开发

    题目 – 一周开发app计划 首批参与成员 -小巫 -墨香 -梦痕 -边城刀客 -徐cc 要求 – -每位认领者按照开源规范来做,代码规范和Android开发规范 -每位认领者必须拥有github账号,熟练使用git对代码进来管理 -每个人认领一个功能点或模块 -提出完善的解决方案并提供封装良好的库

    2024年04月08日
    浏览(34)
  • Android音视频开发(三)——MediaExtractor和MediaMuxer的使用

    了解了音视频的编解码过程,我们接下来使用一下经常跟MediaCodec一起搭配的MediaExtractor和MediaMuxer。最后会使用一个简单的demo来了解具体了解这两个工具类的使用过程。这一节我们就先不讲MediaCodec了,放到下节的demo。 Android提供了一个MediaExtractor类,可以用来 分离容器中的

    2024年02月01日
    浏览(56)
  • Android 音视频开发实践系列-06-初步了解H.264视频编解码技术标准

    本文来自笔者本人的语雀博客,由于语雀升级后不再满足笔者的需求,因此之后笔者会陆续将一些之前已经发布但尚有价值的文章搬家到CSDN。 作为音视频行业从业者,怎么能不理解H.264视频编解码技术标准?本篇文章主要记录笔者学习过程中对众多优秀博客内容的摘抄整理,

    2023年04月09日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包