网站备案期间做什么,最好的淘宝网站建设,WordPress用户中心激活版,苏州制作网页哪家好目录
前言
一、环境
二、创建APP
三. 添加FFmpeg库文件到app中
1. 复制ffmpeg头文件和so库到app中
2. 修改CMakeLists.txt文件内容.
3. 修改ffmpeglib.cpp 文件内容
4. 修改NativeLib.kt 文件添加方法和加载库
5. 调用
四. 增加解析视频文件信息功能
总结 前言 前面…目录
前言
一、环境
二、创建APP
三. 添加FFmpeg库文件到app中
1. 复制ffmpeg头文件和so库到app中
2. 修改CMakeLists.txt文件内容.
3. 修改ffmpeglib.cpp 文件内容
4. 修改NativeLib.kt 文件添加方法和加载库
5. 调用
四. 增加解析视频文件信息功能
总结 前言 前面有一篇记录了windows上 编译Android平台使用的FFmpeg库。想知道的同学可以去看一下。这一篇记录一下在android app上怎么使用这些库。 一、环境
安装Android studio, 方法就不介绍了网上太多安装的方法了。安装NDK和cmake,直接使用SDK monitor安装。 看过我的编译ffmpeg库的知道用的ndk 版本是 25.1.8937393 cmake版本3.22.1 二、创建APP
android studio 创建module 创建成功后目录结构跟下面差不多只是没有assets和cpp这两个文件夹和NatvieLib文件。后面会说这两个文件夹和文件是干嘛的 三. 添加FFmpeg库文件到app中
1. 复制ffmpeg头文件和so库到app中
使用过NDK项目的都知道cpp这个是放CMakeLists.txt和所有的cpp文件的。
cpp这个文件夹下面创建一个ffmpeg文件夹用来放ffmpeg的头文件和so库文件。因为只编译了一个arm64-v8a 架构所在在lib这个文件夹下面创建一个arm64-v8a用于放so库。目录结构如下图 2. 修改CMakeLists.txt文件内容.
修改CMakeLists.txt文件内容编译ffmpeglib.cpp文件。
CMakeLists.txt文件内容如下都添加注释了不多说了。有不太清楚的可以自己创建一个Native library module比对一下看看
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project(ffmpeglib)#设定ffmpeg的头文件和so库文件到一个变量中
set(FFMPEG_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/../cpp/ffmpeg/include)
set(FFMPEG_LIB_DIR ${CMAKE_SOURCE_DIR}/../cpp/ffmpeg/lib/${ANDROID_ABI})# 输出调试信息用于查看路径是否正确
message(STATUS FFMPEG_INCLUDE_DIR: ${FFMPEG_INCLUDE_DIR})
message(STATUS FFMPEG_LIB_DIR: ${FFMPEG_LIB_DIR})# 检查库文件是否存在
file(GLOB FFMPEG_LIB_FILES ${FFMPEG_LIB_DIR}/*.so)
if(NOT FFMPEG_LIB_FILES)message(FATAL_ERROR No FFmpeg library files found in ${FFMPEG_LIB_DIR}. Please check the paths and ensure the libraries exist.)
endif()# 包含FFmpeg头文件只有包含头文件后在cpp中才能正确引用头文件
include_directories(${FFMPEG_INCLUDE_DIR})# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-modules CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED# List C/C source files with relative paths to this CMakeLists.txt.ffmpeglib.cpp)# 显式指定库文件路径
set(avformat_LIBRARY ${FFMPEG_LIB_DIR}/libavformat.so)
set(avcodec_LIBRARY ${FFMPEG_LIB_DIR}/libavcodec.so)
set(avutil_LIBRARY ${FFMPEG_LIB_DIR}/libavutil.so)
set(swresample_LIBRARY ${FFMPEG_LIB_DIR}/libswresample.so)
set(swscale_LIBRARY ${FFMPEG_LIB_DIR}/libswscale.so)
set(avdevice_LIBRARY ${FFMPEG_LIB_DIR}/libavdevice.so)
set(avfilter_LIBRARY ${FFMPEG_LIB_DIR}/libavfilter.so)# 检测so库文件输出找到的库文件路径 c引用so库是不用带lib前缀和.so扩展名的
foreach (LIB avformat avcodec avutil swresample swscale avdevice avfilter)if(EXISTS ${${LIB}_LIBRARY})message(STATUS ${LIB}_LIBRARY: ${${LIB}_LIBRARY})else()message(FATAL_ERROR ${LIB}_LIBRARY not found at ${${LIB}_LIBRARY}. Please check the paths and ensure the libraries exist.)endif()
endforeach()#链接库文件
target_link_libraries(${CMAKE_PROJECT_NAME}${avutil_LIBRARY}${swresample_LIBRARY}${swscale_LIBRARY}${avcodec_LIBRARY}${avdevice_LIBRARY}${avfilter_LIBRARY}${avformat_LIBRARY}# List libraries link to the target libraryc_sharedandroidlog)
3. 修改ffmpeglib.cpp 文件内容
添加一个initFfmpeg 方法用来初始化ffmpeg
extern C JNIEXPORT void JNICALL
Java_com_bob_ffmpegdemo_NativeLib_initFfmpeg(JNIEnv *env, jobject /* this */) {// 初始化 FFmpegavformat_network_init();// 打印 FFmpeg 版本信息到日志const char *version avformat_configuration();LOGD(FFmpeg version: %s, version);
}4. 修改NativeLib.kt 文件添加方法和加载库
package com.bob.ffmpegdemoclass NativeLib {/*** A native method that is implemented by the ffmpglib native library,* which is packaged with this application.*/external fun stringFromJNI(): Stringexternal fun initFfmpeg()companion object {// Used to load the ffmpeglib library on application startup.init {System.loadLibrary(ffmpeglib)}}
}
5. 调用
可以在Activity文件中直接调用NativeLab这个类中和方法 override fun onResume() {super.onResume()testFfmpeg()}private fun testFfmpeg() {val nativeLib NativeLib()Log.d(TAG, -------- ${nativeLib.stringFromJNI()})nativeLib.initFfmpeg()}
直接运行app 成功会输出下面的内容 四. 增加解析视频文件信息功能
通过前面三节内容后ffmpeg的库就添加到app中了但是只是输出了ffmpeg 编译的信息。不知道ffmpeg的功能是否能用。这节增加解析视频文件的功能 直接在ffmpeglib.cpp文件中添加testOpenVideo方法解析视频
extern C JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_testOpenVideo(JNIEnv *env, jobject /* this */, jstring filePath) {const char *path env-GetStringUTFChars(filePath, NULL);// 添加 file:// 前缀以确保正确解析路径std::string full_path file:// std::string(path);LOGD(Attempting to open video file: %s, full_path.c_str());AVFormatContext *pFormatCtx nullptr;int ret avformat_open_input(pFormatCtx, full_path.c_str(), NULL, NULL);if (ret 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));LOGE(Failed to open video file: %s, errbuf);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(Failed to open video file.);}if (avformat_find_stream_info(pFormatCtx, NULL) 0) {LOGE(Failed to retrieve stream information.);avformat_close_input(pFormatCtx);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(Failed to retrieve stream information.);}// 使用正确的函数名 av_dump_formatav_dump_format(pFormatCtx, 0, full_path.c_str(), 0); // 打印格式信息到标准输出// 计算持续时间和比特率int64_t duration pFormatCtx-duration ! AV_NOPTS_VALUE ?av_rescale_q(pFormatCtx-duration, AV_TIME_BASE_Q, {1, 1000}) : -1; // 转换为毫秒int64_t bitrate pFormatCtx-bit_rate / 1000;// 解析流信息并处理无音频流的情况bool hasAudioStream false;for (unsigned int i 0; i pFormatCtx-nb_streams; i) {AVStream *stream pFormatCtx-streams[i];AVCodecParameters *codecpar stream-codecpar;if (codecpar-codec_type AVMEDIA_TYPE_VIDEO) {LOGD(Video Stream: Codec %s, Resolution %dx%d,avcodec_get_name(codecpar-codec_id),codecpar-width, codecpar-height);} else if (codecpar-codec_type AVMEDIA_TYPE_AUDIO) {hasAudioStream true;// 如果 channel_layout 存在则使用它否则提供一个默认值int channels 2; // 默认立体声音频#if LIBAVCODEC_VERSION_INT AV_VERSION_INT(58, 9, 100)if (codecpar-channel_layout) {channels av_get_channel_layout_nb_channels(codecpar-channel_layout);}#endifLOGD(Audio Stream: Codec %s, Sample Rate %d Hz, Channels %d,avcodec_get_name(codecpar-codec_id),codecpar-sample_rate,channels);}}if (!hasAudioStream) {LOGD(No audio streams found in the video file.);}char info[1024];snprintf(info, sizeof(info), Duration: %lld ms, Bitrate: %lld kbps,static_castlong long(duration),static_castlong long(bitrate));avformat_close_input(pFormatCtx);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(info);
}
因为使用的是我自己录制的mp4文件没有声音的。所以添加了hasAudioStream的判断 修改NativeLib.kt 文件
增加
external fun testOpenVideo(path:String): String 方法调用 val outputDir getExternalFilesDir(null)val outputFile File(outputDir, FILENAME)if (!outputFile.exists()) {Log.e(TAG, File does not exist at path: ${outputFile.absolutePath})return} else if (!outputFile.canRead()) {Log.e(TAG, File is not readable at path: ${outputFile.absolutePath})return}val result nativeLib.testOpenVideo(outputFile.absolutePath)Log.d(TAG, -------- $result)
运行成功后 上面贴的CMakeLists.txt的内容已经是完整的。下面贴一下ffmpeglib.cpp, NativeLib.kt 和 MainActivity.kt 完整代码。
ffmpeglib.cpp
#include jni.h
#include string
#include android/log.hextern C {
#include ffmpeg/include/libavformat/avformat.h
}#define LOG_TAG NativeLib
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)extern C JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello Hello from FFmpeg;return env-NewStringUTF(hello.c_str());
}extern C JNIEXPORT void JNICALL
Java_com_bob_ffmpegdemo_NativeLib_initFfmpeg(JNIEnv *env, jobject /* this */) {// 初始化 FFmpegavformat_network_init();// 打印 FFmpeg 版本信息到日志const char *version avformat_configuration();LOGD(FFmpeg version: %s, version);
}extern C JNIEXPORT jstring JNICALL
Java_com_bob_ffmpegdemo_NativeLib_testOpenVideo(JNIEnv *env, jobject /* this */, jstring filePath) {const char *path env-GetStringUTFChars(filePath, NULL);// 添加 file:// 前缀以确保正确解析路径std::string full_path file:// std::string(path);LOGD(Attempting to open video file: %s, full_path.c_str());AVFormatContext *pFormatCtx nullptr;int ret avformat_open_input(pFormatCtx, full_path.c_str(), NULL, NULL);if (ret 0) {char errbuf[AV_ERROR_MAX_STRING_SIZE];av_strerror(ret, errbuf, sizeof(errbuf));LOGE(Failed to open video file: %s, errbuf);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(Failed to open video file.);}if (avformat_find_stream_info(pFormatCtx, NULL) 0) {LOGE(Failed to retrieve stream information.);avformat_close_input(pFormatCtx);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(Failed to retrieve stream information.);}// 使用正确的函数名 av_dump_formatav_dump_format(pFormatCtx, 0, full_path.c_str(), 0); // 打印格式信息到标准输出// 计算持续时间和比特率int64_t duration pFormatCtx-duration ! AV_NOPTS_VALUE ?av_rescale_q(pFormatCtx-duration, AV_TIME_BASE_Q, {1, 1000}) : -1; // 转换为毫秒int64_t bitrate pFormatCtx-bit_rate / 1000;// 解析流信息并处理无音频流的情况bool hasAudioStream false;for (unsigned int i 0; i pFormatCtx-nb_streams; i) {AVStream *stream pFormatCtx-streams[i];AVCodecParameters *codecpar stream-codecpar;if (codecpar-codec_type AVMEDIA_TYPE_VIDEO) {LOGD(Video Stream: Codec %s, Resolution %dx%d,avcodec_get_name(codecpar-codec_id),codecpar-width, codecpar-height);} else if (codecpar-codec_type AVMEDIA_TYPE_AUDIO) {hasAudioStream true;// 如果 channel_layout 存在则使用它否则提供一个默认值int channels 2; // 默认立体声音频#if LIBAVCODEC_VERSION_INT AV_VERSION_INT(58, 9, 100)if (codecpar-channel_layout) {channels av_get_channel_layout_nb_channels(codecpar-channel_layout);}#endifLOGD(Audio Stream: Codec %s, Sample Rate %d Hz, Channels %d,avcodec_get_name(codecpar-codec_id),codecpar-sample_rate,channels);}}if (!hasAudioStream) {LOGD(No audio streams found in the video file.);}char info[1024];snprintf(info, sizeof(info), Duration: %lld ms, Bitrate: %lld kbps,static_castlong long(duration),static_castlong long(bitrate));avformat_close_input(pFormatCtx);env-ReleaseStringUTFChars(filePath, path);return env-NewStringUTF(info);
}
NativeLib.kt
package com.bob.ffmpegdemoclass NativeLib {/*** A native method that is implemented by the ffmpglib native library,* which is packaged with this application.*/external fun stringFromJNI(): Stringexternal fun initFfmpeg()external fun testOpenVideo(path:String): Stringcompanion object {// Used to load the ffmpeglib library on application startup.init {System.loadLibrary(ffmpeglib)}}
} MainActivity.kt
package com.bob.ffmpegdemoimport android.os.Bundle
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.bob.ffmpegdemo.databinding.ActivityMainBinding
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStreamclass MainActivity : AppCompatActivity() {companion object {const val FILENAME abc.mp4const val TAG TAG}private val binding: ActivityMainBinding by lazy {ActivityMainBinding.inflate(layoutInflater)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)enableEdgeToEdge()setContentView(binding.root)ViewCompat.setOnApplyWindowInsetsListener(binding.main) { v, insets -val systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars())v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)insets}copyAssetToFile()}private fun copyAssetToFile() {val dir getExternalFilesDir(null)// Step 1: Create a file object in that directoryval outFile File(dir, FILENAME)// Step 2: Copy the asset file to the external files directorytry {val in: InputStream assets.open(FILENAME)val out: OutputStream FileOutputStream(outFile)val buffer ByteArray(1024)var read: Intwhile ((in.read(buffer).also { read it }) ! -1) {out.write(buffer, 0, read)}in.close()out.flush()out.close()Log.d(TAG, Successfully copied $FILENAME to external files directory.)} catch (e: IOException) {Log.e(TAG, Failed to copy asset file: e.message)}}override fun onResume() {super.onResume()testFfmpeg()}private fun testFfmpeg() {val nativeLib NativeLib()Log.d(TAG, -------- ${nativeLib.stringFromJNI()})nativeLib.initFfmpeg()val outputDir getExternalFilesDir(null)val outputFile File(outputDir, FILENAME)if (!outputFile.exists()) {Log.e(TAG, File does not exist at path: ${outputFile.absolutePath})return} else if (!outputFile.canRead()) {Log.e(TAG, File is not readable at path: ${outputFile.absolutePath})return}val result nativeLib.testOpenVideo(outputFile.absolutePath)Log.d(TAG, -------- $result)}
}
总结
以上就是今天要讲的内容。