上次成功通过FFmpeg采集摄像头的YUV数据,这次针对上一次的程序进行了改造,使用H264编码采集后的数据。

(传送门) JavaCV FFmpeg采集摄像头YUV数据

采集摄像头数据是一个解码过程,而将采集后的数据进行H264编码则是编码过程,如图:



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

javacpp-ffmpeg依赖:

<dependency>
<groupId>org.bytedeco.javacpp-presets</groupId>
<artifactId>ffmpeg</artifactId>
<version>${ffmpeg.version}</version>
</dependency>

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

基本上主要的步骤都是:

  1. 查找编码/解码器
  2. 打开编码/解码器
  3. 进行编码/解码

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

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

  1. 采集视频帧
  2. 将视频帧转化为YUV420P格式
  3. 构建H264编码器
  4. 对视频帧进行编码
采集视频帧

采集视频流中的视频帧在上一次采集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();
}

最终采集效果(H264裸流)可以用VLC播放:

这里对比一下,同样的200帧YUV数据和H264数据的大小,相差还是很大的。

=========================================================

H264编码源码可关注公众号 “HiIT青年” 发送 “ffmpeg-h264” 获取。



关注公众号,阅读更多文章。

JavaCV FFmpeg H264编码的更多相关文章

  1. ffmpeg h264编码 extradata 为空

    ffmpeg h264编码的例子前面的文章已经介绍,本来主要讲述影响AVCodecContext extradata是否为 空的配置项.如果要求open编码器以后AVCodecContext extr ...

  2. JavaCV FFmpeg AAC编码

    上次成功通过FFmpeg采集麦克风的PCM数据,这次针对上一次的程序进行了改造,使用AAC编码采集后的数据. (传送门) JavaCV FFmpeg采集麦克风PCM音频数据 采集麦克风数据是一个解码过 ...

  3. FFmpeg的H264编码有内存泄漏吗??!!!

    靠,内存泄漏好严重.开始怀疑是自己代码问题,调试了半天,又反复改写和优化代码,还是泄漏严重. 拿网上现成的FFMPEG H264编码的范例来测试,同样泄漏很严重. 百度了一下,有很多人遇到同样的问题, ...

  4. 树莓派摄像头模块转成H264编码通过RTMP实现Html输出

    官方原帖 http://www.raspberrypi.org/phpBB3/viewtopic.php?f=43&t=45368&sid=b81f6551e478f0f6e172aa ...

  5. iOS音频AAC视频H264编码 推流最佳方案

    iOS音频AAC视频H264编码 推流最佳方案 项目都是个人的调研与实验,可能很多不好或者不对的地方请多包涵. 1    功能概况 *  实现音视频的数据的采集 *  实现音视频数据的编码,视频编码成 ...

  6. FFMpeg.H264解码win开发环境搭建

    开发环境: vc6.0 + sp5 + vcpp5,注意vcpp5在vc6+sp6上会安装失败. 源码: ff_264_dec_vc,可用vc进行编译调试,但编译环境限定如上. 声明:该工程是ffmp ...

  7. [ffmpeg] h264并行解码

    ffmpeg中的并行解码分为两种: Frame-level Parallelism Slice-level Parallelism Frame-level Parallelism 帧间依赖 我们之前讨 ...

  8. ffmpeg H264 编解码配置

    ffmpeg H264编解码前面有文章介绍下,本文主要介绍一些参数配置. 编码: int InitEncoderCodec( int iWidth, int iHeight) { AVCodec * ...

  9. H264编码原理以及I帧、B和P帧详解, H264码流结构分析

    H264码流结构分析 http://blog.csdn.net/chenchong_219/article/details/37990541 1.码流总体结构: h264的功能分为两层,视频编码层(V ...

随机推荐

  1. 解决Oracle在win10 64使用plsql 无法显示表

    将当前用户切换至所有用户,然后再切换至当前用户,问题解决 图中sql也可查询出当前库中存在的表: select object_name from user_objects where lower(ob ...

  2. 亚马逊DRKG使用体验

    基于文章:探索「老药新用」最短路径:亚马逊AI Lab开源大规模药物重定位知识图谱DRKG,记录了该项目的实际部署与探索过程,供参考. 1. DRKG介绍 大规模药物重定位知识图谱 Drug Repu ...

  3. mysql与redis区别

     编号   mysql redis 1 类型 关系型数据库 缓存数据库 2 作用 主要用于存放持久化数据,是将数据存储在硬盘中,读取时速度较慢,而且频繁访问也会导致数据库负载过高. 用于存储使用频繁的 ...

  4. JAVA 去除实体中类型为string的属性值中的空格

    前端传入的参数实体中,有时候会出现传入了一空格,导致操作失败,这时就可以利用java反射机制去除实体中类型为sting的属性值中的空格. java代码示例: package com.spyang.ut ...

  5. 360浏览器最小字号12的坑 -彻底搞清rem

    之前做响应式网站,使用rem作为单位.因为浏览器的默认字号是16px,设置html {font-size: 62.5%; /*10 ÷ 16 × 100% = 62.5%*/},刚好1rem =10p ...

  6. HTML标签语言一览表

    <html> ● 文件声明 让浏览器知道这是 html 文件 <head> ● 开头 提供文件整体资讯 <title> ● 标题 定义文件标题,将显示于浏览顶端 & ...

  7. centos 遇到/dev/mapper/cl-root 100% 解决方法

    yum安装docker时报错[Errno 2] No usable temporary directory found in ['/tmp', '/var/tmp', '/usr/tmp', '/'] ...

  8. Java虚拟机栈--栈帧

    栈帧的内部结构 每个栈帧中存储着 1.局部变量表(Local Variables) 2.操作数栈(Operand Stack)(或表达式栈) 3.动态链接(Dynamic Linking)(或执行&q ...

  9. Alibaba内部SpringCloud参考笔记,在GitHub一天就标星81.6k?

    前言 阿里巴巴,作为国内互联网公司的Top,算是业界的标杆,有阿里背景的程序员,也更具有权威性.作为程序员,都清楚阿里对于员工要求有多高,技术人员掌握的技术水平更是望尘莫及.所以,大厂程序员的很多经验 ...

  10. netty之handler write

    转载自:https://blog.csdn.net/FishSeeker/article/details/78447684 实验过,例如channle的handler里有很多个outhandler,在 ...