之前用的Android SurfaceView播放視頻是采用的把surface丟到JNI層,在里面更新視圖,這種方式只能渲染 AV_PIX_FMT_RGBA 的格式玄组。但是债沮,由于FFmpeg解碼出來的格式默認(rèn)是YUV的數(shù)據(jù),所以解碼出來之后我們要轉(zhuǎn)換成為RGBA的味滞,這個(gè)轉(zhuǎn)換的操作是很耗時(shí)和耗性能的,所以就需要直接使用YUV數(shù)據(jù)钮呀。
1剑鞍、了解YUV數(shù)據(jù)來源
首先我們要知道,不管是YUV 還是RGBA或者其他的格式爽醋,每一幀數(shù)據(jù)都是存儲在AVFrame里面的蚁署,那么我們就要先了解一下AVFrame。
關(guān)于AVFrame蚂四,網(wǎng)上有很多的介紹光戈,我這里也不多說哪痰,這里給出雷神關(guān)于AVFrame的講解:FFMPEG結(jié)構(gòu)體分析:AVFrame
了解過AVFrame之后,我們知道有兩個(gè)很重要的數(shù)組:
/**
* pointer to the picture/channel planes.
* 圖像數(shù)據(jù)
* This might be different from the first allocated byte
*/
uint8_t *data[AV_NUM_DATA_POINTERS];
/**
* For video, size in bytes of each picture line.
* 對于視頻久妆,每一幀圖象一行的字節(jié)大小晌杰。
* For audio, size in bytes of each plane.
*/
int linesize[AV_NUM_DATA_POINTERS];
雷神說:
uint8_t *data[AV_NUM_DATA_POINTERS]:解碼后原始數(shù)據(jù)(對視頻來說是YUV,RGB筷弦,對音頻來說是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”數(shù)據(jù)的大小肋演。
注意:未必等于圖像的寬,一般大于圖像的寬
所以很清楚烂琴,我們的數(shù)據(jù)就是從這兩個(gè)數(shù)據(jù)里面來獲取YUV數(shù)據(jù)爹殊。
如何在C/C++層獲取YUV數(shù)據(jù),參考雷神的另外一篇文章:FFMPEG 實(shí)現(xiàn) YUV奸绷,RGB各種圖像原始數(shù)據(jù)之間的轉(zhuǎn)換(swscale)
我這里只貼出關(guān)鍵代碼梗夸,具體的去看雷神的帖子
//YUV420P
fwrite(pFrameYUV->data[0],(pCodecCtx->width)*(pCodecCtx->height),1,output);
fwrite(pFrameYUV->data[1],(pCodecCtx->width)*(pCodecCtx->height)/4,1,output);
fwrite(pFrameYUV->data[2],(pCodecCtx->width)*(pCodecCtx->height)/4,1,output);
根據(jù)上面的代碼我們知道,
Y的數(shù)據(jù)的長度=視頻的原始寬(pCodecCtx->width) × 視頻的原始高度(pCodecCtx->height)
u的數(shù)據(jù)的長度 = v = y/4
2健盒、編寫代碼
知道這些之后,開始寫我們今天的代碼
2.1称簿、定義方法:
/**
* JNI 回調(diào)視頻的寬度和高度
*/
private void setMediaSize(int width, int height) { }
/**
* JNI 回調(diào)每一幀的YUV數(shù)據(jù)
*/
private void onDecoder(byte[] yData, byte[] uData, byte[] vData) {}
注:這里主要講YUV的數(shù)據(jù)的回調(diào)扣癣,所以以下代碼無關(guān)乎 setMediaSize(int width, int height)。
2.2憨降、找到Java類里面的方法:
//定義Java類的包名和類名
const char *J_CLASS_NAME = "com/eson/player/MyFPlayerCore";
jclass playerCore;
//java方法
jmethodID onDecoder;
playerCore =jniEnv->FindClass(J_CLASS_NAME);
onDecoder = jniEnv->GetMethodID(playerCore, "onDecoder", "([B[B[B)V");
2.3父虑、獲取YUV數(shù)據(jù)
我們還是用之前寫的onDecoder(AVFrame *avFrame)這個(gè)方法,只需要修改一下就行授药。
JNIEnv *jniEnv;
jbyteArray yArray;
jbyteArray uArray;
jbyteArray vArray;
int length = 0;
unsigned char *ydata;
unsigned char *udata;
unsigned char *vdata;
void VideoCallBack::onDecoder(AVFrame *avFrame) {
// LOGE("onDecoder (AVFrame)");
if (w_width == 0 || w_height == 0) {
return;
}
if (!avFrame) {
return;
}
//這里只是獲取到數(shù)據(jù)的指針
ydata = avFrame->data[0];
udata = avFrame->data[1];
vdata = avFrame->data[2];
//剛開始讀數(shù)據(jù)前幾幀數(shù)據(jù)有空數(shù)據(jù)士嚎,不知道為什么
if (ydata == NULL || udata == NULL || vdata == NULL) {
return;
}
//數(shù)據(jù)的長度,即Java byte[] 的長度
if (length == 0) {
length = w_width * w_height;
}
if (jniEnv == NULL) {
jniEnv = callJavaUtil->getCurrentJNIEnv();
LOGE(" got new jnienv");
}
//只初始化一次長度
if (yArray == NULL) {
yArray = jniEnv->NewByteArray(length);
LOGE(" got new yArray");
}
jniEnv->SetByteArrayRegion(yArray, 0, length, (jbyte *) ydata);
if (uArray == NULL) {
uArray = jniEnv->NewByteArray(length / 4);
LOGE(" got new uArray");
}
jniEnv->SetByteArrayRegion(uArray, 0, length / 4, (jbyte *) udata);
if (vArray == NULL) {
vArray = jniEnv->NewByteArray(length / 4);
LOGE(" got new vArray");
}
jniEnv->SetByteArrayRegion(vArray, 0, length / 4, (jbyte *) vdata);
//回調(diào)
callJavaUtil->callOnDecoder(jniEnv, yArray, uArray, vArray);
}
我們再打印一下linesize的長度悔叽,看一下與視頻寬度(w_height)的關(guān)系
LOGE("avFrame->linesize[0] ----->>>%d",avFrame->linesize[0]);
LOGE("avFrame->linesize[1] ----->>>%d",avFrame->linesize[1]);
LOGE("avFrame->linesize[2] ----->>>%d",avFrame->linesize[2]);
經(jīng)過幾個(gè)視頻的測試會(huì)發(fā)現(xiàn)莱衩,avFrame->linesize[0] 始終是avFrame->linesize[1]和avFrame->linesize[2]的2倍,而avFrame->linesize[0] 和w_height是相等的娇澎”恳希可雷神說是不總是相等的,那怎么辦?
2.4趟庄、完善
后來經(jīng)過查資料括细,在這里找到了解決方法:ffmpeg從AVFrame取出yuv數(shù)據(jù)到保存到char*中
參照他的方法我對代碼進(jìn)行了修改:
JNIEnv *jniEnv;
jbyteArray yArray;
jbyteArray uArray;
jbyteArray vArray;
int length = 0;
unsigned char *ydata;
unsigned char *udata;
unsigned char *vdata;
//新的yuv數(shù)據(jù)
uint8_t *newY = NULL;
uint8_t *newU = NULL;
uint8_t *newV = NULL;
void VideoCallBack::onDecoder(AVFrame *avFrame) {
// LOGE("onDecoder (AVFrame)");
if (w_width == 0 || w_height == 0) {
return;
}
if (!avFrame) {
return;
}
ydata = avFrame->data[0];
udata = avFrame->data[1];
vdata = avFrame->data[2];
if (ydata == NULL || udata == NULL || vdata == NULL) {
return;
}
//長度不變,不改變原始圖像的寬高
if (length == 0) {
length = w_width * w_height;
}
//重新申請一個(gè)與所需相同的內(nèi)存
if (newY == NULL) {
newY = (uint8_t *) av_malloc(length * sizeof(uint8_t));
}
if (newU == NULL) {
newU = (uint8_t *) av_malloc(length / 4 * sizeof(uint8_t));
}
if (newV == NULL) {
newV = (uint8_t *) av_malloc(length / 4 * sizeof(uint8_t));
}
//把原始數(shù)據(jù)復(fù)制到申請的內(nèi)存里面
for (int i = 0; i < w_height; i++) {
memcpy(newY + w_width * i,
ydata + avFrame->linesize[0] * i,
w_width);
}
for (int j = 0; j < w_height / 2; j++) {
memcpy(newU + w_width / 2 * j,
udata + avFrame->linesize[1] * j,
w_width / 2);
}
for (int k = 0; k < w_height / 2; k++) {
memcpy(newV + w_width / 2 * k,
vdata + avFrame->linesize[2] * k,
w_width / 2);
}
if (jniEnv == NULL) {
jniEnv = callJavaUtil->getCurrentJNIEnv();
}
//只初始化一次長度
if (yArray == NULL) {
yArray = jniEnv->NewByteArray(length);
}
//把新的數(shù)據(jù)放到byte[]里面
jniEnv->SetByteArrayRegion(yArray, 0, length, (jbyte *) newY);
if (uArray == NULL) {
uArray = jniEnv->NewByteArray(length / 4);
}
if (uArray == NULL){
return;
}
jniEnv->SetByteArrayRegion(uArray, 0, length / 4, (jbyte *) newU);
if (vArray == NULL) {
vArray = jniEnv->NewByteArray(length / 4);
}
if (vArray == NULL){
return;
}
jniEnv->SetByteArrayRegion(vArray, 0, length / 4, (jbyte *) newV);
//回調(diào)
callJavaUtil->callOnDecoder(jniEnv, yArray, uArray, vArray);
}
到這里戚啥,AVFrame里面的YUV數(shù)據(jù)就以byte[]的方式傳遞到了Java層了奋单。
最后說一下,由于之前公司離職猫十,所以關(guān)于視頻解碼這一塊的文章估計(jì)會(huì)停更览濒,也或許不會(huì)呆盖,看情況吧。