FFmpeg 使用详解

FFmpeg官方介绍文档:http://ffmpeg.org/doxygen/trunk/

 

 

FFmpeg的架构

FFmpeg包含以下几个库:

  1. libavutil:工具库,用来辅助进行简单的多媒体编程。包括安全的字符串函数,随机数生成器,一些数据结构,扩展的数学函数,基于编码与多媒体的功能。
  2. libavcodec:编解码相关的库,提供通用的编解码框架,并且包含了多种用于音频流,视频流,字幕流的编解码器,并且包含了一些比特流过滤器(filter)。
  3. libavformat:一个为音频流,视频流,字幕流提供通用多路复用(multiplexing/muxing)与反多路复用(demultiplexing/demuxing)框架的库。包含很多适合各种多媒体格式的复用器(muxer)与解复用器(demuxer)。
  4. libavdevice:一个通用框架库,用来从多媒体设备中抓取多媒体流,或将其渲染至多媒体设备,并且支持许多种设备,包括Video4Linux2, VfW, DShow, ALSA等。
  5. libavfilter:通用音视频过滤(filtering)库,包含多种过滤器,源与接收器。
  6. libswscale:此库提供高度优化的图像缩放,颜色空间与像素格式转换操作。
  7. libswresample:此库提供高度优化的音频重采样,缩放(rematrix)与采样格式转换等操作。

 

常用的数据结构

Ps.数据结构中包含的变量与数据结构列表不做赘述,再用到的时候在做介绍,简介见JavaDoc

AVCodecContext

FFmpeg中主要的扩展数据结构,包含了编解码信息,音频/视频/字幕流信息等,并且提供了一系列函数来访问或修改这些数据。

AVFormatContext

FFmpeg中另外一个重要的数据结构,包含格式化的I/O上下文,主要用来进行I/O相关的操作,默认使用avformat_alloc_context()来初始化,用处与其字面意思一样,由于Java上的FFmpeg本质上还是JNI技术的一种实现,所以原理同C差不多,也是需要先开辟内存。

AVIOContext

包含字节流I/O上下文,用来管理输入输出数据,同样默认需要使用avio_alloc_context()来进行初始化。

AVFrame

这个数据结构包含了已解码的(raw)音频或视频数据。AVFrame必须使用av_frame_alloc()来初始化,需要注意的是这只会初始化AVFrame自身,具体的缓冲区数据需要使用其他方式来管理。在使用过后,AVFrame还需要使用av_frame_free()来释放。

 

音视频的编解码

对于多媒体编程来说,最为重要的就是多媒体数据的编解码,而编解码器(Codec)是音视频编解码的核心部分,主要涉及到了AVCodecContext这个数据结构。

容器的概念

容器(Container)封装了一个多媒体文件所需要的所有多媒体数据,包括音频,视频,字幕等,需要注意的是,容器的封装格式仅仅描述了多媒体文件以什么方式保存,外部表现为文件的后缀名,例如mp3,mp4,mkv等,并没有描述多媒体数据的具体编码方式,所以只看多媒体文件的后缀名,并不能确定其编码方式。

流与帧

流(Stream)是一种有序的数据载体,在FFmpeg中,可以表现为视频流,音频流,字幕流。流中的某一具体数据元素称为帧(Frame)。

FFmpeg的视频解码过程

一般来说,FFmpeg的视频解码分为以下几个步骤:

  1. 注册所有的容器格式以及其相对应的Codec  av_register_all()
  2. 打开文件,将其读入AVFormatContext中      avformat_open_input()
  3. 从文件中提取流信息                                      avformat_find_stream_info()
  4. 从多个数据流中找到需要的流
  5. 查询流所对应的解码器                                  avcodec_find_decoder()
  6. 打开解码器                                                    avcodec_open2()
  7. 为解码帧分配内存                                         av_frame_alloc()
  8. 将数据从流读入至Packet                               av_read_frame()
  9. 对视频帧进行解码                                          avcodec_decode_video2()

音视频Metadata的获取

Ps.以下是FFmpeg-JavaCPP的Metadata获取方式。

由于原FFmpeg是C编写的,而其JavaCPP实现是运用了JNI技术将其移植至Java,所以在写法上Metadata的获取还是与C有着很大的相似的。PHSS的Metadata读取函数如下:

  1. public Map<String, Object> readMetaData(Path path) throws Exception {
  2. av_register_all();
  3. Map<String, Object> metadataFullMap = new HashMap<>();
  4. Map<String, Object> metadataCurrentMap = new HashMap<>();
  5. AVFormatContext avFormatContext = avformat_alloc_context();
  6. AVDictionaryEntry entry = null;
  7. avformat_open_input(avFormatContext, path.toString(), null, null);
  8. avformat_find_stream_info(avFormatContext, ((PointerPointer) null));
  9. while ((entry = av_dict_get(avFormatContext.metadata(), "", entry, AV_DICT_IGNORE_SUFFIX)) != null) {
  10. metadataFullMap.put(entry.key().getString(), entry.value().getString());
  11. }
  12. for (String key : metadataList) {
  13. if (metadataFullMap.get(key) != null) {
  14. metadataCurrentMap.put(key, metadataFullMap.get(key));
  15. } else {
  16. metadataCurrentMap.put(key, "");
  17. }
  18. }
  19. metadataCurrentMap.put("duration", formatDuration(avFormatContext.duration()));
  20. metadataCurrentMap.put("bitrate", formatBitrate(avFormatContext.streams(0).codecpar().bit_rate()));
  21. metadataCurrentMap.put("sample_rate", avFormatContext.streams(0).codecpar().sample_rate());
  22. metadataCurrentMap.put("bit_depth", avFormatContext.streams(0).codecpar().bits_per_raw_sample());
  23. metadataCurrentMap.put("size", formatSize(path.toFile().length()));
  24. avformat_close_input(avFormatContext);
  25. return metadataCurrentMap;
  26. }

首先是通过av_register_all()函数来注册所有相关的组件,然后使用avformat_alloc_context()函数来新建一个AVFormatContext对象,此对象用来存储多媒体对象的基本构成信息。声明一个AVDictionaryEntry来储存音轨的元数据信息。

使用avformat_open_input()函数来将音轨写入avFormatContext的buffer中,然后使用av_dict_get()函数来将avFormatContext中结构化的metadata数据读入AVDictionaryEntry,然后便可以将AVDictionaryEntry中的元数据读出。

音频封面的获取

由于metadata只包含文字形式的数据,所以需要通过别的方法来获取音频的封面。由于处于FFmpeg的版本更替中,所以封面获取的相关类非常不稳定,因此PHSS暂时选择了FFmpeg-JavaCPP的上层实现,也就是JavaCV来实现音频封面的获取。

  1. public byte[] getArtwork(Path path) throws Exception {
  2. FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(path.toFile());
  3. Java2DFrameConverter converter = new Java2DFrameConverter();
  4. grabber.start();
  5. BufferedImage bufferedImage = converter.getBufferedImage(grabber.grabImage());
  6. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  7. ImageIO.write(bufferedImage, "jpg", outputStream);
  8. grabber.close();
  9. return outputStream.toByteArray();
  10. }

 

FFmpeg的Time base系统以及duration的计算

对于time base(时基)的概念,首先需要介绍关于音视频同步的简单知识。

 

参考文献:

https://protogalaxy.me/ffmpeg%E4%B8%8E%E5%85%B6javacpp%E5%AE%9E%E7%8E%B0%E8%B8%A9%E5%9D%91%E7%BA%AA%E5%BD%95/

https://zh.wikipedia.org/wiki/Java%E6%9C%AC%E5%9C%B0%E6%8E%A5%E5%8F%A3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值