FFmpeg視頻播放--YUV輸出

之前用的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ì)呆盖,看情況吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匾七,一起剝皮案震驚了整個(gè)濱河市絮短,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌昨忆,老刑警劉巖丁频,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異邑贴,居然都是意外死亡席里,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門拢驾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奖磁,“玉大人,你說我怎么就攤上這事繁疤】” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵稠腊,是天一觀的道長躁染。 經(jīng)常有香客問我,道長架忌,這世上最難降的妖魔是什么吞彤? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮叹放,結(jié)果婚禮上饰恕,老公的妹妹穿的比我還像新娘。我一直安慰自己井仰,他們只是感情好埋嵌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俱恶,像睡著了一般莉恼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上速那,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天俐银,我揣著相機(jī)與錄音,去河邊找鬼端仰。 笑死捶惜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荔烧。 我是一名探鬼主播吱七,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼汽久,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了踊餐?” 一聲冷哼從身側(cè)響起景醇,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吝岭,沒想到半個(gè)月后三痰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窜管,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年散劫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幕帆。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡获搏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出失乾,到底是詐尸還是另有隱情常熙,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布碱茁,位于F島的核電站裸卫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏早芭。R本人自食惡果不足惜彼城,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一诅蝶、第九天 我趴在偏房一處隱蔽的房頂上張望退个。 院中可真熱鬧,春花似錦调炬、人聲如沸语盈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刀荒。三九已至,卻和暖如春棘钞,著一層夾襖步出監(jiān)牢的瞬間缠借,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工宜猜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留泼返,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓姨拥,卻偏偏與公主長得像绅喉,于是被迫代替她去往敵國和親渠鸽。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,676評論 0 3
  • 前言 代碼github地址https://github.com/ccj659/NDK-FFmpeg-master ...
    Chauncey_Chen閱讀 3,831評論 3 31
  • 本篇博客在雷神的結(jié)構(gòu)體介紹基礎(chǔ)上按自己的喜好整理的 后面根據(jù)自己工作中所需有所增改 AVStream 存儲每一個(gè)視...
    石丘閱讀 2,463評論 1 10
  • 就地取材廣廈閑柴罐, 清風(fēng)滿院彩云翩徽缚。 采得野味留遠(yuǎn)客, 共語藍(lán)天樂忘還革屠。
    秋雨敲窗閱讀 132評論 0 0
  • 煩躁的時(shí)候總是會(huì)控制不住自己凿试,心塞
    童Toni閱讀 141評論 0 0