javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据
javacpp-ffmpeg系列:
javacpp-FFmpeg系列之1:视频拉流解码成YUVJ420P,并保存为jpg图片
javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据
javacpp-FFmpeg系列之3: 图像数据转换(BGR与BufferdImage互转,RGB与BufferdImage互转)
前言:
第一篇中视频解码成YUVJ420P图像像素数据(以下简称YUV或YUV数据),只是YUV在流媒体协议中用的较多(数据少,节省流量带宽),在图像处理应用较多的是BGR和RGB像素数据。我们已经获取到了YUV数据,那么把YUV转成BGR或者RGB需要再进行一次转换,显然性能上的表现并不是很好,所以本篇会通过编写一个通用转换器来介绍如何使用ffmpeg解码转出BGR、RGB、YUV等像素数据。
补充:
(1)为什么暂时没有用python实现,主要是先熟悉ffmpeg的API,后面会出的
(2)为什么要转成BGR、RGB像素数据,因为有了这俩其中一个就可以直接生成java的BufferImage了啊,最重要的是我们本意不是要转成BufferImage,而是直接编码成base64的图像数据啊
(3)演示demo见下一章
一、功能设计
第一篇写的很简略(实际上是那一大坨代码,自己实在看不下去了qaq),直接参考ffmpeg原生C的API,不符合java语言编写习惯,所以本篇会对上篇代码进行一些简单的封装复用。
功能上,会支持多种格式的像素数据(BGR、RGB、YUV等等);代码上,会对各个流程进行阐述分析。
二、功能实现
(1)初始化
加载ffmpeg的网络库和编解码库,不初始化就没法用,适合放在静态块中进行加载
static {
// Register all formats and codecs
av_register_all();
avformat_network_init();
}
(2)打开视频流
初始化AVFormatContext,主要就是根据url创建InputStream,并且会根据不同协议(rtmp/rtsp/hls/文件等)尝试读取一些文件头数据(流媒体头数据)。
补充:FileNotOpenException是继承自RuntimeException的自定义异常类,只是加个名字方便标识异常而已,下面还会有几个异常,都是继承自RuntimeException的自定义异常类,以下不会再提
/**
* 打开视频流
* @param url -url
* @return
* @throws FileNotOpenException
*/
protected AVFormatContext openInput(String url) throws FileNotOpenException{
AVFormatContext pFormatCtx = new AVFormatContext(null);
if(avformat_open_input(pFormatCtx, url, null, null)==0) {
return pFormatCtx;
}
throw new FileNotOpenException("Didn't open video file");
}
(3)检索流信息
上一步获得了AVFormatContext,这一步继续根据AVFormatContext读取一部分视音频数据并且获得一些相关的信息
/**
* 检索流信息
* @param pFormatCtx
* @return
*/
protected AVFormatContext findStreamInfo(AVFormatContext pFormatCtx) throws StreamInfoNotFoundException{
if (avformat_find_stream_info(pFormatCtx, (PointerPointer<?>) null)>= 0) {
return pFormatCtx;
}
throw new StreamInfoNotFoundException("Didn't retrieve stream information");
}
(3)获取视频帧
上面两步确定了媒体文件/流的上下文,这一步尝试读取一帧视频帧。
分成两个方法,先获取视频帧位置,然后根据位置获取视频帧,当然也可以合成一个方法使用。
/**
* 获取第一帧视频位置
* @param pFormatCtx
* @return
*/
protected int findVideoStreamIndex(AVFormatContext pFormatCtx) {
int i = 0, videoStream = -1;
for (i = 0; i < pFormatCtx.nb_streams(); i++) {
AVStream stream=pFormatCtx.streams(i);
AVCodecContext codec=stream.codec();
if (codec.codec_type() == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
return videoStream;
}
/**
* 指定视频帧位置获取对应视频帧
* @param pFormatCtx
* @param videoStream
* @return
*/
protected AVCodecContext findVideoStream(AVFormatContext pFormatCtx ,int videoStream)throws StreamNotFoundException {
if(videoStream >=0) {
// Get a pointer to the codec context for the video stream
AVStream stream=pFormatCtx.streams(videoStream);
AVCodecContext pCodecCtx = stream.codec();
return pCodecCtx;
}
throw new StreamNotFoundException("Didn't open video file");
}
(4)查找编解码器
其实底层调用的还是find_encdec(),遍历AVCodec链表查找有没有对应的编解码器,如果没有找到直接抛出异常,如果已经确定编解码,也可以指定codec_id
/**
* 查找并尝试打开解码器
* @return
*/
protected AVCodecContext findAndOpenCodec(AVCodecContext pCodecCtx) {
// Find the decoder for the video stream
AVCodec pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
if (pCodec == null) {
System.err.println("Codec not found");
throw new CodecNotFoundExpception("Codec not found");
}
AVDictionary optionsDict = null;
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
System.err.println("Could not open codec");
throw new CodecNotFoundExpception("Could not open codec"); // Could not open codec
}
return pCodecCtx;
}
(5.1)循环读取视频帧并解码成yuv
这个没什么好讲的了,前面的准备任务做完,就是一直循环读取视频帧,最后解码出来的图像帧都是yuv像素数据,这个显然不是我们想要的,所以要对这里进行改动
// Allocate video frame
AVFrame pFrame = av_frame_alloc();AVPacket packet = new AVPacket();
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index() == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
// Did we get a video frame?
if (frameFinished != null&&frameFinished[0] != 0) {//ffmpeg默认解码出来的是yuv数据
System.err.println(pFrame.data());
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(packet);
}
(5.2)循环读取视频帧并转换成RGB或BGR图像像素数据
// Allocate video frame
AVFrame pFrame = av_frame_alloc();
//Allocate an AVFrame structure
AVFrame pFrameRGB = av_frame_alloc();width = pCodecCtx.width();
height = pCodecCtx.height();
pFrameRGB.width(width);
pFrameRGB.height(height);
pFrameRGB.format(fmt);// Determine required buffer size and allocate buffer
int numBytes = avpicture_get_size(fmt, width, height);SwsContext sws_ctx = sws_getContext(width, height,
pCodecCtx.pix_fmt(), width, height,fmt, SWS_BILINEAR, null, null,
(DoublePointer) null);BytePointer buffer = new BytePointer(av_malloc(numBytes));
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill(new AVPicture(pFrameRGB), buffer, fmt, width, height);
AVPacket packet = new AVPacket();
int[] frameFinished = new int[1];
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index() == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
// Did we get a video frame?
if (frameFinished != null&&frameFinished[0] != 0) {
// Convert the image from its native format to BGR
sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, height, pFrameRGB.data(),pFrameRGB.linesize());
//Convert BGR to ByteBuffer//保存RGB或BGR数据
return saveFrame(pFrameRGB, width, height);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(packet);
}
三、完整代码
package cc.eguid.cv.corelib.videoimageshot.grabber;
import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;
import static org.bytedeco.javacpp.swscale.*;import java.io.IOException;
import java.nio.ByteBuffer;import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.DoublePointer;
import org.bytedeco.javacpp.PointerPointer;import cc.eguid.cv.corelib.videoimageshot.exception.CodecNotFoundExpception;
import cc.eguid.cv.corelib.videoimageshot.exception.FileNotOpenException;
import cc.eguid.cv.corelib.videoimageshot.exception.StreamInfoNotFoundException;
import cc.eguid.cv.corelib.videoimageshot.exception.StreamNotFoundException;public abstract class GrabberTmplate {
static {
// Register all formats and codecs
av_register_all();
avformat_network_init();
}
//保留,暂不用
protected int width;//宽度
protected int height;//高度
/**
* 打开视频流
* @param url -url
* @return
* @throws FileNotOpenException
*/
protected AVFormatContext openInput(String url) throws FileNotOpenException{
AVFormatContext pFormatCtx = new AVFormatContext(null);
if(avformat_open_input(pFormatCtx, url, null, null)==0) {
return pFormatCtx;
}
throw new FileNotOpenException("Didn't open video file");
}
/**
* 检索流信息
* @param pFormatCtx
* @return
*/
protected AVFormatContext findStreamInfo(AVFormatContext pFormatCtx) throws StreamInfoNotFoundException{
if (avformat_find_stream_info(pFormatCtx, (PointerPointer<?>) null)>= 0) {
return pFormatCtx;
}
throw new StreamInfoNotFoundException("Didn't retrieve stream information");
}
/**
* 获取第一帧视频位置
* @param pFormatCtx
* @return
*/
protected int findVideoStreamIndex(AVFormatContext pFormatCtx) {
int i = 0, videoStream = -1;
for (i = 0; i < pFormatCtx.nb_streams(); i++) {
AVStream stream=pFormatCtx.streams(i);
AVCodecContext codec=stream.codec();
if (codec.codec_type() == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
return videoStream;
}
/**
* 指定视频帧位置获取对应视频帧
* @param pFormatCtx
* @param videoStream
* @return
*/
protected AVCodecContext findVideoStream(AVFormatContext pFormatCtx ,int videoStream)throws StreamNotFoundException {
if(videoStream >=0) {
// Get a pointer to the codec context for the video stream
AVStream stream=pFormatCtx.streams(videoStream);
AVCodecContext pCodecCtx = stream.codec();
return pCodecCtx;
}
throw new StreamNotFoundException("Didn't open video file");
}
/**
* 查找并尝试打开解码器
* @return
*/
protected AVCodecContext findAndOpenCodec(AVCodecContext pCodecCtx) {
// Find the decoder for the video stream
AVCodec pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
if (pCodec == null) {
System.err.println("Codec not found");
throw new CodecNotFoundExpception("Codec not found");
}
AVDictionary optionsDict = null;
// Open codec
if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
System.err.println("Could not open codec");
throw new CodecNotFoundExpception("Could not open codec"); // Could not open codec
}
return pCodecCtx;
}/**
* 抓取视频帧(默认跳过音频帧和空帧)
* @param url -视频源(rtsp/rtmp/hls/文件等等)
* @param fmt - 像素格式,比如AV_PIX_FMT_BGR24
* @return
* @throws IOException
*/
public ByteBuffer grabVideoFrame(String url,int fmt) throws IOException {
// Open video file
AVFormatContext pFormatCtx=openInput(url);// Retrieve stream information
pFormatCtx=findStreamInfo(pFormatCtx);// Dump information about file onto standard error
//av_dump_format(pFormatCtx, 0, url, 0);//Find a video stream
int videoStream=findVideoStreamIndex(pFormatCtx);
AVCodecContext pCodecCtx =findVideoStream(pFormatCtx,videoStream);
// Find the decoder for the video stream
pCodecCtx= findAndOpenCodec(pCodecCtx);// Allocate video frame
AVFrame pFrame = av_frame_alloc();
//Allocate an AVFrame structure
AVFrame pFrameRGB = av_frame_alloc();width = pCodecCtx.width();
height = pCodecCtx.height();
pFrameRGB.width(width);
pFrameRGB.height(height);
pFrameRGB.format(fmt);// Determine required buffer size and allocate buffer
int numBytes = avpicture_get_size(fmt, width, height);SwsContext sws_ctx = sws_getContext(width, height,
pCodecCtx.pix_fmt(), width, height,fmt, SWS_BILINEAR, null, null,
(DoublePointer) null);BytePointer buffer = new BytePointer(av_malloc(numBytes));
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill(new AVPicture(pFrameRGB), buffer, fmt, width, height);
AVPacket packet = new AVPacket();
int[] frameFinished = new int[1];
try {
// Read frames and save first five frames to disk
while (av_read_frame(pFormatCtx, packet) >= 0) {
// Is this a packet from the video stream?
if (packet.stream_index() == videoStream) {
// Decode video frame
avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);
// Did we get a video frame?
if (frameFinished != null&&frameFinished[0] != 0) {
// Convert the image from its native format to BGR
sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, height, pFrameRGB.data(),pFrameRGB.linesize());
//Convert BGR to ByteBuffer
return saveFrame(pFrameRGB, width, height);
}
}
// Free the packet that was allocated by av_read_frame
av_free_packet(packet);
}
return null;
}finally {
//Don't free buffer
// av_free(buffer);
av_free(pFrameRGB);// Free the RGB image
av_free(pFrame);// Free the YUV frame
sws_freeContext(sws_ctx);//Free SwsContext
avcodec_close(pCodecCtx);// Close the codec
avformat_close_input(pFormatCtx);// Close the video file
}
}
/**
* BGR图像帧转字节缓冲区(BGR结构)
*
* @param pFrame
* -bgr图像帧
* @param width
* -宽度
* @param height
* -高度
* @return
* @throws IOException
*/
abstract ByteBuffer saveFrame(AVFrame pFrameRGB, int width, int height);
}
四、小结
本章主要详解ffmpeg拉流解码的各个流程,可以通过本章代码可以轻松实现不限于RGB/BGR/YUV的FFmpeg所支持的多种像素格式转换
javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转换为YUV、BGR24或RGB24等图像像素数据的更多相关文章
- javacpp-FFmpeg系列之1:视频拉流解码成YUVJ420P,并保存为jpg图片
javacpp-ffmpeg系列: javacpp-FFmpeg系列之1:视频拉流解码成YUVJ420P,并保存为jpg图片 javacpp-FFmpeg系列之2:通用拉流解码器,支持视频拉流解码并转 ...
- 视音频数据处理入门:H.264视频码流解析
===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...
- Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- iOS - 直播流程,视频推流,视频拉流,简介,SMTP、RTMP、HLS、 PLPlayerKit
收藏笔记 1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据涉及技术或协议:摄像机: ...
- 【转】直播流程,视频推流,视频拉流,简介,SMTP、RTMP、HLS、 PLPlayerKit
原:https://www.cnblogs.com/baitongtong/p/11248966.html 1 .音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放 ...
- ffmpeg 系列博客
https://www.ffmpeg.org/download.html#build-macffmpeg 系列博文https://me.csdn.net/blog/leixiaohua1020http ...
- 前端提升生产力系列三(vant3 vue3 移动端H5下拉刷新,上拉加载组件的封装)
| 在日常的移动端开发中,经常会遇到列表的展示,以及数据量变多的情况下还会有上拉和下拉的操作.进入新公司后发现移动端好多列表,但是在看代码的时候发现,每个列表都是单独的代码,没有任何的封装,都是通过v ...
- 海康&大华&DSS视频拉流-RTSP转RTMP多媒体播放技术
海康&大华&DSS获取RTSP 实时流 海康:rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/ ...
- Storm概念学习系列之Stream消息流 和 Stream Grouping 消息流组
不多说,直接上干货! Stream消息流是Storm中最关键的抽象,是一个没有边界的Tuple序列. Stream Grouping 消息流组是用来定义一个流如何分配到Tuple到Bolt. Stre ...
随机推荐
- ie63像素bug原因及解决办法不使用hack
1.浮动元素后边跟不浮动元素时会产生3像素bug 2.解决办法是不要忘记给浮动元素的相邻元素加上浮动.
- 华为AI应用创新大赛即将开启!公开课已备好!
为鼓励开发者创新,挖掘前沿创新能力的应用及服务,帮开发者打造爆款应用的同时丰富终端消费者的用户体验,由设立10亿激励基金耀星计划扶持的华为创新竞赛平台即将开启. 竞赛平台将滚动推出AI.HAG.AR. ...
- PHP中__get()和__set()的用法实例详解
php面向对象_get(),_set()的用法 一般来说,总是把类的属性定义为private,这更符合现实的逻辑.但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数“__ge ...
- zabbix-agent active 配置自动探测
1. zabbix-agent 被动模式配置文件: PidFile=/var/run/zabbix/zabbix_agentd.pid LogFile=/var/log/zabbix/zabbix_a ...
- erlang中的图片下载
问题如题,这是在一个群里问的一个的问题.其实就是http的Server的上传下载的功能. ibrowse:start().ibrowse:send_req("http://img1.gti ...
- 深入Asyncio(十)异步解析式
Async Comprehensions 目前已经学会了如何在Python中进行异步迭代,接下来的问题是这是否适用于解析式?答案是OJBK!该支持在PEP 530中提及,建议去读一下. >> ...
- 如何给UIViewController瘦身
本文转载至 http://www.cocoachina.com/ios/20141128/10356.html 随着程序逻辑复杂度的提高,你是否也发现了App中一些ViewController的代码 ...
- Java 并发随身记(一)之 Unsafe类
最近在看Java并发相关的内容,需要自己整理整理,不然就生疏了.工作2年多,工作时一般注都是框架.消息这些内容,对基础内容比较忽视.闲话不说,既然是并发内容,首先先复习一下Unsafe的内容吧. Un ...
- python 基础 9.1 连接数据库
二.数据库连接 MySQLdb 提供了connect 方法用来和数据库建立连接,接收数个参数,返回连接对象: #/usr/bin/python #coding=utf-8 #@Time :2017 ...
- WEB服务器、应用程序服务器、HTTP服务器区别【转】
WEB服务器.应用程序服务器.HTTP服务器有何区别?IIS.Apache.Tomcat.Weblogic.WebSphere都各属于哪种服务器,这些问题困惑了很久,今天终于梳理清楚了: Web服务器 ...