【音视频编解码】记录、转换、流化音视频的 FFmpeg 开发思路

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

FFmpeg 概述

  • libavcodec 音视频编码或解码的核心代码
  • libavdeivce 操作计算机中常用的音视频捕获或输出设备:ALSA、AUDIO_BEOS、JACK等
  • libavfilter 音视频滤波器的开发,宽高比、裁剪、格式化、非格式化、伸缩等
  • libavformat 音视频流的格式解析,为libavcodec分析码流提供独立的音频或视频码流源
  • libavutil 实用工具库,包括算术运算、字符操作等
  • libpostproc 音视频应用的后处理,如图像的去块效应
  • libswscale 图像颜色空间或格式转换,如RGB的24、32等于YUV的420等之间的转换

初始化流程

首先自己想了想,如果要我做,怎么做?思路:找到音视频,解码,保存,音视频同步,输出到屏幕等,初步应该是这个思路吧!看了ffmpeg后,清楚大概流程应该为:

  • 1、完成FFMPEG支持的编解码格式注册av_register_all,avcodec_register_all;

  • 2、打开本地or网络服务端的多媒体文件av_open_input_file;

  • 3、分别找音/视频的流索引ID信息av_find_stream_info;

  • 4、根据ID信息寻找相应的解码器avcodec_find_decoder;

  • 5、打开解码器avcodec_open;

  • 6、开辟缓存avcodec_alloc_frame;

    (开始对音/视频解码avcodec_decode_video;)

    (图象进行 YUV 和 RGB之间的转换img_convert;)

openVideo打开视频

还是想想若是自己做,是个什么思路:要打开视频,即开始解码,再将解码后的音/视频输出显示即可,但是如何做到音视频同步了?思考,但是下载的源码中已经实现了同步,并且封装了,不能查看。

  • 开始对音/视频解码avcodec_decode_video,即initVideo、initAudio;
  • 用surfaceView类将视频显示;
startVideo播放视频
  • 开缓存、读解码后的信息,读完后释放开辟的缓存。

以上是对播放视频流程的基本了解

FFmpeg 日志输出系统介绍

FFmpeg 日志输出的核心函数方法为: av_log() 。为什么说av_log()是FFmpeg中输出日志的核心函数函数?

因为我们随便打开一个FFmpeg的源代码文件,就会发现其中遍布着av_log()函数。一般情况下FFmpeg类库的源代码不允许使用printf()这种函数,所有的输出一律使用的av_log()。

av_log() 函数说明

av_log()的声明位于libavutil\log.h,具体的声明代码如下:

/**
 * Send the specified message to the log if the level is less than or equal
 * to the current av_log_level. By default, all logging messages are sent to
 * stderr. This behavior can be altered by setting a different logging callback
 * function.
 * @see av_log_set_callback
 *
 * @param avcl A pointer to an arbitrary struct of which the first field is a
 *        pointer to an AVClass struct.
 * @param level The importance level of the message expressed using a @ref
 *        lavu_log_constants "Logging Constant".
 * @param fmt The format string (printf-compatible) that specifies how
 *        subsequent arguments are converted to output.
 */
void av_log(void *avcl, int level, const char *fmt, ...) av_printf_format(3, 4);

其中第一个参数指定该log所属的结构体,例如AVFormatContext、AVCodecContext等等。第二个参数指定log的级别,第三个参数为要输出的内容,源代码中定义了如下几个级别:

/**
 * Print no output.
 */
#define AV_LOG_QUIET    -8
 
/**
 * Something went really wrong and we will crash now.
 */
#define AV_LOG_PANIC     0
 
/**
 * Something went wrong and recovery is not possible.
 * For example, no header was found for a format which depends
 * on headers or an illegal combination of parameters is used.
 */
#define AV_LOG_FATAL     8
 
/**
 * Something went wrong and cannot losslessly be recovered.
 * However, not all future data is affected.
 */
#define AV_LOG_ERROR    16
 
/**
 * Something somehow does not look correct. This may or may not
 * lead to problems. An example would be the use of '-vstrict -2'.
 */
#define AV_LOG_WARNING  24
 
/**
 * Standard information.
 */
#define AV_LOG_INFO     32
 
/**
 * Detailed information.
 */
#define AV_LOG_VERBOSE  40
 
/**
 * Stuff which is only useful for libav* developers.
 */
#define AV_LOG_DEBUG    48

从定义中可以看出来,av_log()的日志级别分别是:

  • AV_LOG_PANIC,AV_LOG_FATAL,
  • AV_LOG_ERROR,AV_LOG_WARNING,
  • AV_LOG_INFO,AV_LOG_VERBOSE,
  • AV_LOG_DEBUG。

每个级别定义的数值代表了严重程度,数值越小代表越严重。

默认av_log()输出的级别是AV_LOG_INFO。

设置日志输出等级

在上面,我们讲到av_log()函数是可以设置日志的内容的等级的。而对于输出的日志内容,我们也是可以设置等级的。FFmpeg提供了av_log_set_level()用于设置当前Log的级别。

函数声明如下:

/**
 * Set the log level
 *
 * @see lavu_log_constants
 *
 * @param level Logging level
 */
void av_log_set_level(int level);

查看函数代码实现:

static int av_log_level = AV_LOG_INFO;

可以看出,设置日志输出等级主要是操作静态全局变量av_log_level。该变量用于存储当前系统Log的级别。

日志输出实战

通过下面的代码,我们就可以理解上面讲的日志输出及设置日志输出等级的逻辑了。

#include "pch.h"
#include <iostream>

extern "C"{
#include "libavutil/log.h"
}

int main(int argc, char* argv[]) {
    av_log_set_level(AV_LOG_ERROR);
    av_log(NULL, AV_LOG_INFO, "Hello World\n");
    return 0;
}

自定义FFmpeg日志输出

从文章开头的函数调用图可以看到,av_log()调用了av_vlog(),av_log()调用了一个函数指针av_log_callback。av_log_callback是一个全局静态变量,定义如下所示:

static void (*av_log_callback)(void*, int, const char*, va_list) = av_log_default_callback;

从代码中可以看出,av_log_callback指针默认指向一个函数av_log_default_callback()。av_log_default_callback()即FFmpeg默认的Log函数。

需要注意的是,这个Log函数是可以自定义的。按照指定的参数定义一个自定义的函数后,可以通过FFmpeg的另一个API函数av_log_set_callback()设定为Log函数。

查看源码,可以看到 av_log_set_callback() 的声明如下:

/**
 * Set the logging callback
 *
 * @note The callback must be thread safe, even if the application does not use
 *       threads itself as some codecs are multithreaded.
 *
 * @see av_log_default_callback
 *
 * @param callback A logging function with a compatible signature.
 */
void av_log_set_callback(void (*callback)(void*, int, const char*, va_list));

从声明中可以看出,需要指定一个参数为(void*, int, const char*, va_list),返回值为void的函数作为Log函数。

查看av_log_set_callback() 源码,可以看到此方法只是做了一个函数指针赋值的工作,代码如下:

void av_log_set_callback(void (*callback)(void*, int, const char*, va_list)) {
    av_log_callback = callback;
}

这样我们可以自定义一个my_logoutput()函数作为Log的输出函数:

void my_logoutput(void* ptr, int level, const char* fmt,va_list vl){
    ****(省略....}

编辑好函数之后,使用av_log_set_callback()函数设置该函数为Log输出函数即可。

av_log_set_callback(my_logoutput);

下面是自定义日志输出的实例源码:

#include "pch.h"
#include <iostream>

extern "C"{
#include "libavutil/log.h"
}

void my_logoutput(void* ptr, int level, const char* fmt, va_list vl) {
    printf("Hello Log Output! Content = %s", fmt);
}

int main(int argc, char* argv[]) {
    av_log_set_callback(my_logoutput);  // 设置自定义的日志输出方法
    av_log(NULL, AV_LOG_INFO, "Hello World\n");
    return 0;
}

尾述

FFmpeg 是一个强大的专门音视频处理的库,很多播放器都是基于 FFmpeg 进行的开发;文章所提的功能只是罗列了 FFmpeg 可使用功能的一部分

更多 FFmpeg 相关的学习文档;可在评论区下方留言,或者私信发送 “音视频进阶”,即可获取一份高级音视频学习手册;希望这份手册能够给大家学习音视频带来一些帮助

【音视频编解码】记录、转换、流化音视频的 FFmpeg 开发思路_第1张图片

好了,以上就是今天要分享的内容,大家觉得有用的话,可以点赞分享一下;如果文章中有什么问题欢迎大家指正;欢迎在评论区或后台讨论哈~

你可能感兴趣的:(Android开发,音视频开发,Android工程师,ffmpeg,音视频,视频编解码,ui,android)