介绍
上一篇文章Android 利用V4L2 调用camera介绍了使用V4L2 接口预览camera的基本方法。目前接触过的usb camera支持的图像格式基本上只包括3种:
- YUV
- MJPEG
- H264
其中YUV是原始数据,MJPEG和H264都是压缩编码的数据。所以对于MJPEG和H264需要先解码为原始图像数据,才能给到android显示。这篇文章主要介绍对MJPEG数据的处理
准备
先看一下此次修改的效果图
- UI
让用户自己选择对应的格式和分辨率 - 解码
一般可采用的有opencv,ffmpeg,libyuv。此次采用libyuv,libyuv是一个谷歌的开源项目,跨平台,处理速度很快。针对此次MJPEG处理是比较合适的。libyuv解码MJPEG需要用到libjpeg
camera使用的基本流程在文章Android 利用V4L2 调用camera已经介绍过,
正常的流程主要是以下几步
- SurfaceView创建
- SurfaceView创建成功回调
- 打开camera
- camera打开成功回调
- 获取camera参数
- 弹框用户选择对应分辨率
- 设置对应pixformat和分辨率
- 开始预览
- 获取到MJPEG数据后,利用libyuv解码
- Android nativieWindow显示
此次大体流程没有变化,着重介绍修改的地方
让用户选择预览图像格式和分辨率
在camera打开成功的回调种,即 CameraStateCallback 的回调onOpened 获取usb camera支持的参数,camera参数格式可以参见文章Android 利用V4L2 调用camera中的获取camera的参数
class CameraStateCallback implements IStateCallback {
@Override
public void onOpened() {
Log.d(TAG, "onOpened");
parameters = adCamera.getCameraParameters();
pixformats = new String[parameters.size()];
resolutions = new String[parameters.size()][];
String sPixFormat;
for (int i = 0; i < parameters.size(); i++) {
Log.e(TAG, "format:" + parameters.get(i).pixFormat);
switch (parameters.get(i).pixFormat) {
case YUYV:
sPixFormat = "YUYV";
break;
case MJPEG:
sPixFormat = "MJPEG";
break;
case H264:
sPixFormat = "H264";
break;
default:
sPixFormat = "UNKNOW";
break;
}
pixformats[i] = sPixFormat;
int resolutionSize = parameters.get(i).frames.size();
resolutions[i] = new String[resolutionSize];
for (int j = 0; j < resolutionSize; j++) {
String resolution = parameters.get(i).frames.get(j).width + "*" + parameters.get(i).frames.get(j).height;
resolutions[i][j] = resolution;
}
}
showDialog();
}
将获取到的参数解析到以下两个数组中
String[] pixformats;
String[][] resolutions;
dialog采用加载ExpandableListView,这里就不详细介绍。
点击确认后,设置预览参数并开始预览
ret = adCamera.setPreviewParameter(previewWidth, previewHeight, parameters.get(pixClick).pixFormat);
surfaceView.setAspectRatio(previewWidth, previewHeight);
...
adCamera.setSurface(surfaceHolder);
...
cameraDataCallback = new CameraDataCallback();
adCamera.startPreview(cameraDataCallback);
libjpeg库的编译移植与使用
这里使用AS编译libJPEG-turbo源码
-
新建Android工程libjpeg,并将libjpeg-turbo源码全部拷贝到src/main/cpp目录下
libjpeg.png 修改Android工程的build.gradle,配置libjpeg-turbo的CmakeLists.txt
defaultConfig {
applicationId "com.test.libjpeg"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags ""
// 配置编译的平台版本
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
externalNativeBuild {
cmake {
path "src\\main\\cpp\\CMakeLists.txt"//路径改为cpp文件夹下CMakeList的路径
}
}
- 编译工程,生成对应的so和.h 文件
编译过程很顺利,没碰到什么问题。倒是从网上下载下来的总是有编译错误,所以这里就不提供工程了。
将libjpeg 生成的so和h文件添加到AnV4L2Camera工程中
-
在v4l2camera模块cpp文件下新建libjpeg文件夹,将libjpeg几个相关头文件拷贝到该目录下
includelibjpeg.png
其中 jconfig.h 在工程libjpeg以下目录
./app/.cxx/cmake/debug/arm64-v8a/jconfig.h
./app/.cxx/cmake/debug/armeabi-v7a/jconfig.h
其他头文件都在在工程libjpeg以下目录
./app/src/main/cpp/
-
在 v4l2camera模块新建文件夹src\main\jniLibs,将libjpeg.so拷贝到该文件夹下
libjpegso.png 修改v4l2camera模块 CMakeLists.txt
# 指定libjpeg头文件的路径
include_directories(include libjpeg)
# 指定libjpeg动态库路径
set(jpeglibs "${CMAKE_SOURCE_DIR}/../jniLibs")
# 导入第三方库:libjpeg.so
add_library(libjpeg SHARED IMPORTED)
set_target_properties(libjpeg PROPERTIES
IMPORTED_LOCATION "${jpeglibs}/${ANDROID_ABI}/libjpeg.so")
target_link_libraries( # Specifies the target library.
v4l-android
android
libjpeg
# Links the target library to the log library
# included in the NDK.
${log-lib})
libyuv移植
这里采用直接将libyuv源码导入到AnV4L2Camera工程中
- 下载libyuv https://chromium.googlesource.com/libyuv/libyuv
- 将 libyuv 源码 include 目录下的 libyuv 目录下的头文件和 libyuv.h 一起拷贝到 v4l2camera模块下的 src\main\cpp\include目录
- 将 libyuv 源码 source 目录下的全部文件拷贝到 v4l2camera模块新建文件夹src\main\cpp\libyuv
- 修改v4l2camera模块 CMakeLists.txt
# 导入libyuv头文件路径
include_directories(include libjpeg)
# 打开宏HAVE_JPEG,libyuv才会去编译和使用libjpeg
add_definitions(-DHAVE_JPEG)
# 导入libyuv 源文件路径
file(GLOB src_files *.cpp libyuv/source/*.cc)
libyuv解码MJPEG
int src_width = 0;
int src_height = 0;
libyuv::MJPGSize(raw, rSize, &src_width, &src_height);
//经图片保存,16进制查看保存的改格式为 64 82 35 ff -- B G R A
//stride 跨距, 它描述一行像素中, 该颜色分量所占的 byte 数目
libyuv::MJPGToARGB(raw, rSize, preview, width * 4, src_width,
src_height, width, height);
#ifdef SAVE_JPEG
if (!WRITE_FILE) {
const char *path = "/sdcard/argb.bmp"; // 路径
bmp_write(preview, width, height, path);
WRITE_FILE = true;
}
#endif
//WINDOW_FORMAT_RGBA_8888 排列顺序为 R G B A
unsigned char temp;
for (int i = 0; i < width * height * 4; i = i + 4) {
temp = preview[i+2];
preview[i+2] = preview[i];
preview[i] = temp;
}
raw是通过v4l2获取到的mjpeg格式数据,主要通过libyuv::MJPGToARGB将数据转换成rgba数据。 通过将转换后的数据保存成bmp,用hex格式打开发现,数据保存的格式为BGRA,这个可能windows上或bmp格式的数据就是用这种方式保存的,属于little endian。 android上WINDOW_FORMAT_RGBA_8888 排列顺序为 RGBA,所以还需要做下转换,颜色才能正常。
相关代码已经更新到demo https://github.com/yizhongliu/AnV4L2Camera
参考
https://blog.csdn.net/AndrExpert/article/details/100123845
https://blog.csdn.net/tyyj90/article/details/120294921