介紹
上一篇文章Android 利用V4L2 調(diào)用camera介紹了使用V4L2 接口預覽camera的基本方法。目前接觸過的usb camera支持的圖像格式基本上只包括3種:
- YUV
- MJPEG
- H264
其中YUV是原始數(shù)據(jù),MJPEG和H264都是壓縮編碼的數(shù)據(jù)衰猛。所以對于MJPEG和H264需要先解碼為原始圖像數(shù)據(jù),才能給到android顯示霸琴。這篇文章主要介紹對MJPEG數(shù)據(jù)的處理
準備
先看一下此次修改的效果圖
- UI
讓用戶自己選擇對應的格式和分辨率 - 解碼
一般可采用的有opencv,ffmpeg,libyuv严卖。此次采用libyuv烟具,libyuv是一個谷歌的開源項目梢什,跨平臺,處理速度很快朝聋。針對此次MJPEG處理是比較合適的嗡午。libyuv解碼MJPEG需要用到libjpeg
camera使用的基本流程在文章Android 利用V4L2 調(diào)用camera已經(jīng)介紹過,
正常的流程主要是以下幾步
- SurfaceView創(chuàng)建
- SurfaceView創(chuàng)建成功回調(diào)
- 打開camera
- camera打開成功回調(diào)
- 獲取camera參數(shù)
- 彈框用戶選擇對應分辨率
- 設置對應pixformat和分辨率
- 開始預覽
- 獲取到MJPEG數(shù)據(jù)后冀痕,利用libyuv解碼
- Android nativieWindow顯示
此次大體流程沒有變化荔睹,著重介紹修改的地方
讓用戶選擇預覽圖像格式和分辨率
在camera打開成功的回調(diào)種,即 CameraStateCallback 的回調(diào)onOpened 獲取usb camera支持的參數(shù)金度,camera參數(shù)格式可以參見文章Android 利用V4L2 調(diào)用camera中的獲取camera的參數(shù)
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();
}
將獲取到的參數(shù)解析到以下兩個數(shù)組中
String[] pixformats;
String[][] resolutions;
dialog采用加載ExpandableListView应媚,這里就不詳細介紹。
點擊確認后猜极,設置預覽參數(shù)并開始預覽
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"http://路徑改為cpp文件夾下CMakeList的路徑
}
}
- 編譯工程跟伏,生成對應的so和.h 文件
編譯過程很順利丢胚,沒碰到什么問題。倒是從網(wǎng)上下載下來的總是有編譯錯誤受扳,所以這里就不提供工程了携龟。
將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動態(tài)庫路徑
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);
//經(jīng)圖片保存坟桅,16進制查看保存的改格式為 64 82 35 ff -- B G R A
//stride 跨距, 它描述一行像素中, 該顏色分量所占的 byte 數(shù)目
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格式數(shù)據(jù),主要通過libyuv::MJPGToARGB將數(shù)據(jù)轉(zhuǎn)換成rgba數(shù)據(jù)蕊蝗。 通過將轉(zhuǎn)換后的數(shù)據(jù)保存成bmp仅乓,用hex格式打開發(fā)現(xiàn),數(shù)據(jù)保存的格式為BGRA蓬戚,這個可能windows上或bmp格式的數(shù)據(jù)就是用這種方式保存的夸楣,屬于little endian。 android上WINDOW_FORMAT_RGBA_8888 排列順序為 RGBA子漩,所以還需要做下轉(zhuǎn)換豫喧,顏色才能正常。
相關代碼已經(jīng)更新到demo https://github.com/yizhongliu/AnV4L2Camera
參考
https://blog.csdn.net/AndrExpert/article/details/100123845
https://blog.csdn.net/tyyj90/article/details/120294921