JavaCV FFmpeg H264编码

从上图可以看出,编码过程,数据流是从AVFrame流向AVPacket,而解码过程正好相反,数据流是从AVPacket流向AVFrame。

javacpp-ffmpeg依赖:

<dependency>

    <groupId>org.bytedeco.javacpp-presets</groupId>

    <artifactId>ffmpeg</artifactId>

    <version>${ffmpeg.version}</version>

</dependency>

FFmpeg编码的过程是解码的逆过程,不过主线流程是类似的,如下图:

基本上主要的步骤都是:

查找编码/解码器

打开编码/解码器

进行编码/解码

在FFmpeg的demo流程中其实还有创建流avformat_new_stream(),写入头部信息avformat_write_header()和尾部信息av_write_trailer()等操作,这里只是将YUV数据编码成H264裸流,所以可以暂时不需要考虑这些操作。

将采集视频流数据进行H264编码的整体流程主要有以下几个步骤:

采集视频帧

将视频帧转化为YUV420P格式

构建H264编码器

对视频帧进行编码

采集视频帧

采集视频流中的视频帧在上一次采集YUV数据的时候已经实现了,主要是从AVFormatContext中用av_read_frame()读取视频数据并进行解码(avcodec_decode_video2()),实现代码如下:

public AVFrame grab() throws FFmpegException {

    if (av_read_frame(pFormatCtx, pkt) >= 0 && pkt.stream_index() == videoIdx) {

        ret = avcodec_decode_video2(pCodecCtx, pFrame, got, pkt);

        if (ret < 0) {

            throw new FFmpegException(ret, "avcodec_decode_video2 解码失败");

        }

        if (got[0] != 0) {

            return videoConverter.scale(pFrame);

        }

        av_packet_unref(pkt);

    }

    return null;

}

这样通过grab()方法就可以获取到视频流中的视频帧了。

将视频帧转化为YUV420P格式

在进行H264编码之前一定要确保视频帧是YUV420P格式的,所以必须对采集到的视频帧做一次转化,用到的是FFmpeg的SwsContext组件,下面的VideoConverter是对SwsContext封装的组件,内部实现了AVFrame的填充及SwsContext的初始化,使用方式如下:

// 1. 创建VideoConverter,指定转化格式为AV_PIX_FMT_YUV420P

videoConverter = VideoConverter.create(videoWidth, videoHeight, pCodecCtx.pix_fmt(),

    videoWidth, videoHeight, AV_PIX_FMT_YUV420P);

// 2. 对视频帧进行转化

videoConverter.scale(pFrame);

VideoConvert的scale方式,实际上也是调用了SwsContext的scale方法:

sws_scale(swsContext, new PointerPointer<>(pFrame), pFrame.linesize(),

    0, srcSliceH, new PointerPointer<>(avFrame), avFrame.linesize());

构建H264编码器

进行H264编码之前需要构建H264编码器,根据上面的流程图利用avcodec_find_encoder()和avcodec_alloc_context3()实现编码器的创建和参数配置,最后用avcodec_open()打开编码器,完整的初始化代码如下:

public static VideoH264Encoder create(int width, int height, int fps, Map<String, String> opts)

        throws FFmpegException {

    VideoH264Encoder h = new VideoH264Encoder();

    // 查找H264编码器

    h.pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);

    if (h.pCodec == null) {

        throw new FFmpegException("初始化 AV_CODEC_ID_H264 编码器失败");

    }

    // 初始化编码器信息

    h.pCodecCtx = avcodec_alloc_context3(h.pCodec);

    h.pCodecCtx.codec_id(AV_CODEC_ID_H264);

    h.pCodecCtx.codec_type(AVMEDIA_TYPE_VIDEO);

    h.pCodecCtx.pix_fmt(AV_PIX_FMT_YUV420P);

    h.pCodecCtx.width(width);

    h.pCodecCtx.height(height);

    h.pCodecCtx.time_base().num(1);

    h.pCodecCtx.time_base().den(fps);

    // 其他参数设置

    AVDictionary dictionary = new AVDictionary();

    opts.forEach((k, v) -> {

        avutil.av_dict_set(dictionary, k, v, 0);

    });

    h.ret = avcodec_open2(h.pCodecCtx, h.pCodec, dictionary);

    if (h.ret < 0) {

        throw new FFmpegException(h.ret, "avcodec_open2 编码器打开失败");

    }

    h.pkt = new AVPacket();

    return h;

}

参数说明

width:视频的宽度

height:视频的高度

fps:视频的帧率

opts:编码器的其他参数设置

对视频帧进行编码

编码器构建完成后就可以对视频帧进行编码了,入参为AVFrame,出参为byte[](这里也可以是AVPacket,由于需要将H264裸流写入文件,这里直接返回byte数组)

public byte[] encode(AVFrame avFrame) throws FFmpegException {

    if (avFrame == null) {

        return null;

    }

    byte[] bf = null;

    try {

        avFrame.format(pCodecCtx.pix_fmt());

        avFrame.width(pCodecCtx.width());

        avFrame.height(pCodecCtx.height());

        ret = avcodec_encode_video2(pCodecCtx, pkt, avFrame, got);

        if (ret < 0) {

            throw new FFmpegException(ret, "avcodec_encode_video2 编码失败");

        }

        if (got[0] != 0) {

            bf = new byte[pkt.size()];

            pkt.data().get(bf);

        }

        av_packet_unref(pkt);

    } catch (Exception e) {

        throw new FFmpegException(e.getMessage());

    }

    return bf;

}

最后只需要调整一下上一次的主程序,将读取YUV数据的部分,调整为将AVFrame丢进编码器,拉取byte数组即可。

public static void main(String[] args) throws FFmpegException, IOException, InterruptedException {

    int fps = 25;

    avdevice_register_all();

    av_register_all();

    VideoGrabber g = new VideoGrabber();

    g.open("Integrated Camera");

    VideoH264Encoder encoder = VideoH264Encoder.create(g.getVideoWidth(), g.getVideoHeight(), fps);

    OutputStream fos = new FileOutputStream("yuv420p.h264");

    for (int i = 0; i < 200; i++) {

        AVFrame avFrame = g.grab();

        byte[] buf = encoder.encode(avFrame);

        if (buf != null) {

            fos.write(buf);

        }

        Thread.sleep(1000 / fps);

    }

    fos.flush();

    fos.close();

    encoder.release();

    g.close();

}

深圳网站建设www.sz886.com

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352