使用ffmpeg + ndk21讀取視頻幀并轉(zhuǎn)為bitmap

流程:ffmpeg讀取視頻幀的yuv-> jni層創(chuàng)建Bitmap,拿到bitmap表示像素?cái)?shù)據(jù)的指針->將YUV轉(zhuǎn)換到bitmap的像素?cái)?shù)據(jù)中(ARGB_8888)

一. ffmpeg讀取視頻幀的yuv

這里只處理格式為yuv420p的視頻幀

初始化AVFormatContext

    const char *cstr = videoPath.c_str();

    LOGD("inputFmtContext = %p", iFmtContext);
    
    
    //打開(kāi)AVFormatContext,用于解封裝的上下文
    int ret = avformat_open_input(&iFmtContext, cstr, nullptr, nullptr);

    if (ret != 0) {
        LOGE("avformat_open_input file %s failed,%s", cstr, av_err2str(ret));
        return;
    }

    LOGI("av_find_best_stream file %s success", cstr);

    avformat_find_stream_info(iFmtContext, nullptr);
    
    //找到視頻流在iFmtContext內(nèi)部數(shù)組里的索引
    int videoIndex = av_find_best_stream(iFmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, -1);

    if (videoIndex < 0) {
        LOGE("av_find_best_stream file %s failed,%d", cstr, videoIndex);
        return;
    }

    videoStream = iFmtContext->streams[videoIndex];
    LOGD("video stream index = %d,duration = %lu,real duration = %f", videoIndex,
         videoStream->duration, videoStream->duration * timeBaseToDuration(videoStream->time_base));

打開(kāi)解碼器,并獲取YUV


    if (!iCodecContext) {

       //查找解碼器
        AVCodec *avCodec = avcodec_find_decoder(videoStream->codecpar->codec_id);
        if (!avCodec) {
            LOGW("getFrameAt avcodec_find_decoder failed");
            return nullptr;
        }

        LOGD2(LOG_TAG, "codec name:%s", avCodec->name);
        iCodecContext = avcodec_alloc_context3(avCodec);
        if (!iCodecContext) {
            LOGW("getFrameAt avcodec_alloc_context3 failed");
            return nullptr;
        }

       //從AVStream里面復(fù)制解碼參數(shù)
        int err = avcodec_parameters_to_context(iCodecContext, videoStream->codecpar);
        if (err < 0) {
            LOGW("getFrameAt avcodec_parameters_to_context failed,err:%s", av_err2str(err));
            return nullptr;
        }

        err = avcodec_open2(iCodecContext, avCodec, nullptr);
        if (err < 0) {
            LOGW("getFrameAt avcodec_open2 failed,err:%s", av_err2str(err));
            return nullptr;
        }
    }

    LOGI("codec init success!!!");

    // 未解碼數(shù)據(jù)結(jié)構(gòu)體
    AVPacket *packet = av_packet_alloc();

    //已解碼數(shù)據(jù)結(jié)構(gòu)體
    AVFrame *frame = av_frame_alloc();

    int64_t frameNum = 0;


    int length = 0;
    int read = 0;

  // seek到指定時(shí)間cuo
    int seek = av_seek_frame(iFmtContext, videoStream->index,
                             timeMills / 1000 / timeBaseToDuration(videoStream->time_base),
                             AVSEEK_FLAG_BACKWARD);

    if (seek < 0) {
        LOGW("seek failed,code:%d", seek);
        goto end;
    }

    while (!(read = av_read_frame(iFmtContext, packet))) {

        LOGD2(LOG_TAG, "packet index:%d", packet->stream_index);
        if (packet->stream_index == videoStream->index) {
            //LOGD("read frame:%" PRId64 ,frameNum);
            //將數(shù)據(jù)發(fā)送到解碼器解碼
            int code = avcodec_send_packet(iCodecContext, packet);
            if (code != 0) {
                LOGW("avcodec_send_packet failed");
                av_packet_unref(packet);
                break;
            }

            frameNum++;
            int ret = 0;
            int num = 0;
          
            //讀取解碼后的視頻數(shù)據(jù)
            if ((ret = avcodec_receive_frame(iCodecContext, frame)) == AVERROR(EAGAIN)) {
                LOGD("avcodec_receive_frame ret:%d,", ret);
                continue;
            }

            if (!ret) {
                num++;
                LOGD("single codec return:%d,ret:%d", num, ret);
                LOGD("frame width: %d,height: %d", frame->width, frame->height);

                // writeSingleFrame2File(frame);
                // yuv4202RGB(frame);
              
                //這里拿到的frame數(shù)據(jù)就包含一幀yuv視頻數(shù)據(jù)了
                yuv420ToRgb(frame, rgb);

            }
            if (ret < 0) {
                LOGW("avcodec_receive_frame err:%d,%s", ret, av_err2str(ret));
            }


            av_packet_unref(packet);

            break;

        }
    }

    LOGD("frame num:%" PRId64 ",frame read:%" PRId64 ",read %d", videoStream->nb_frames, frameNum,
         read);

    end:
    av_packet_free(&packet);
    av_frame_free(&frame);



二. jni層創(chuàng)建Bitmap,拿到bitmap表示像素?cái)?shù)據(jù)的指針

  1. native創(chuàng)建一個(gè)bitmap
static jobject createBitmap(JNIEnv *env, int width, int height) {
    jclass bitmapCls = env->FindClass("android/graphics/Bitmap");

    if (!bitmapCls) {
        LOGW("bitmapCls failed");
        return nullptr;
    }
    jmethodID createBitmapFunction = env->GetStaticMethodID(bitmapCls,"createBitmap",
                                                            "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");

    if (!createBitmapFunction) {
        LOGW("createBitmapFunction failed");
        return nullptr;
    }

    jstring configName = env->NewStringUTF("ARGB_8888");
    jclass bitmapConfigClass = env->FindClass("android/graphics/Bitmap$Config");
    jmethodID valueOfBitmapConfigFunction = env->GetStaticMethodID(
            bitmapConfigClass, "valueOf",
            "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;");

    if (!valueOfBitmapConfigFunction) {
        LOGW("valueOfBitmapConfigFunction failed");
        return nullptr;
    }

    LOGI("valueOfBitmapConfigFunction success");

    jobject bitmapConfig = env->CallStaticObjectMethod(bitmapConfigClass,
                                                       valueOfBitmapConfigFunction,configName);

    jobject bitmap = env->CallStaticObjectMethod(bitmapCls,
                                                 createBitmapFunction,
                                                 width,
                                                 height, bitmapConfig);

    return bitmap;
}

2 拿到bitmap表示像素?cái)?shù)據(jù)的指針
需要添加內(nèi)置本地庫(kù): jnigraphics

  jobject bitmap = createBitmap(env, width, height);

    int ret;
    uint8_t *rgbData = nullptr;
    //AndroidBitmap_lockPixels后,rgbData就指向bitmap的像素?cái)?shù)據(jù)了
    if ((ret = AndroidBitmap_lockPixels(env, bitmap, (void**)&rgbData)) < 0) {
        LOGW("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return nullptr;
    }


    LOGD("AndroidBitmap_lockPixels ret=%d", ret);

    reader->getFrameAt(time_mills,&rgbData);
    LOGD("getFrameAt end");
     //TODO
    AndroidBitmap_unlockPixels(env, bitmap);

  //返回bitmap到j(luò)ava層
   return bitmap;

三 將YUV數(shù)據(jù)轉(zhuǎn)換到bitmap的像素?cái)?shù)據(jù)中


static void yuv420ToRgb(AVFrame *frame, uint8_t **rgb) {
    int img_width = frame->width;
    int img_height = frame->height;
    //int buffer_len = frame->width * frame->height;
    //uint8_t *buffer = static_cast<uint8_t *>(malloc(sizeof(uint8_t) * buffer_len * 4));
    int channels = 4;

    uint8_t *buffer = *rgb;

    for (int y = 0; y < img_height; y++) {
        for (int x = 0; x < img_width; x++) {

            //linesize[0]表示一行Y數(shù)據(jù)需要多少字節(jié)存儲(chǔ), 由于字節(jié)對(duì)齊的優(yōu)化,一般會(huì)大于圖片的寬度,例如,測(cè)試視頻linesize[0]為864,img_width為854
            int indexY = y * frame->linesize[0] + x;
            int indexU = y / 2 * frame->linesize[1] + x / 2;
            int indexV = y / 2 * frame->linesize[2] + x / 2;
            uint8_t Y = frame->data[0][indexY];
            uint8_t U = frame->data[1][indexU];
            uint8_t V = frame->data[2][indexV];

            // 這里可以參考YUV420轉(zhuǎn)rgb公式
            int R = Y + 1.402 * (V - 128);  // 由于計(jì)算的結(jié)果可能不在0~255之間,所以R不能用uint8_t表示
            int G = Y - 0.34413 * (U - 128) - 0.71414 * (V - 128);
            int B = Y + 1.772 * (U - 128);
            R = (R < 0) ? 0 : R;
            G = (G < 0) ? 0 : G;
            B = (B < 0) ? 0 : B;
            R = (R > 255) ? 255 : R;
            G = (G > 255) ? 255 : G;
            B = (B > 255) ? 255 : B;
            buffer[(y * img_width + x) * channels + 0] = (uint8_t) R;
            buffer[(y * img_width + x) * channels + 1] = (uint8_t) G;
            buffer[(y * img_width + x) * channels + 2] = (uint8_t) B;
            //補(bǔ)充 alpha通道數(shù)據(jù), android轉(zhuǎn)bitmap需要
            buffer[(y * img_width + x) * channels + 3] = 0xff;
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蹄咖,更是在濱河造成了極大的恐慌纠拔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜜氨,死亡現(xiàn)場(chǎng)離奇詭異边琉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)记劝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)变姨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人厌丑,你說(shuō)我怎么就攤上這事定欧。” “怎么了怒竿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵砍鸠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我耕驰,道長(zhǎng)爷辱,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任朦肘,我火速辦了婚禮饭弓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媒抠。我一直安慰自己弟断,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布趴生。 她就那樣靜靜地躺著阀趴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苍匆。 梳的紋絲不亂的頭發(fā)上刘急,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音浸踩,去河邊找鬼叔汁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的攻柠。 我是一名探鬼主播球订,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼瑰钮!你這毒婦竟也來(lái)了冒滩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤浪谴,失蹤者是張志新(化名)和其女友劉穎开睡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體苟耻,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡篇恒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凶杖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁艰。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖智蝠,靈堂內(nèi)的尸體忽然破棺而出腾么,到底是詐尸還是另有隱情,我是刑警寧澤杈湾,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布解虱,位于F島的核電站,受9級(jí)特大地震影響漆撞,放射性物質(zhì)發(fā)生泄漏殴泰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一浮驳、第九天 我趴在偏房一處隱蔽的房頂上張望悍汛。 院中可真熱鬧,春花似錦抹恳、人聲如沸员凝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至旺上,卻和暖如春瓶蚂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宣吱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工窃这, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人征候。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓杭攻,卻偏偏與公主長(zhǎng)得像祟敛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子兆解,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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